Compile-Time Checks with static_assert
A static_assert is evaluated by the compiler, not by the running program.
Two Checks Before Reading On
Programs often rely on assumptions about the types they use. A program might assume that a char can hold negative values, or that an int can hold numbers in the hundred-thousands. Those assumptions depend on the platform the code is compiled for, so they are not always guaranteed. C++ lets you state such an assumption directly in your code and have the compiler check it.
- The first line claims that the largest int value on this platform is at least 100000.
- The second line claims that char is a signed type on this platform.
- Each message is shown only if its condition is false.
What do you think happens?
Will both checks pass on every platform that compiles this code?
Reveal answer
No, the results depend on the platform
The source says the assumptions about types depend on the platform the code is compiled for, so they are not always guaranteed. Either check can pass on one platform and fail on another.
What numeric_limits Holds
numeric_limits is a standard library class, declared in the header <limits>, that provides properties of built-in types such as the maximum exponent of a float or the number of bytes in an int.
The class is a lookup table of facts about types, not a container of values. You do not store data in numeric_limits. You ask it a question about a type, and it answers with a property of that type. The two representative properties in the source are the maximum exponent of a floating-point type and the number of bytes in an int.
Every query follows one pattern. You write numeric_limits, then a type name inside angle brackets, then the scope operator, then the name of the property. The angle brackets are needed because numeric_limits is a template: the same class works for many types, and the brackets say which type you are asking about.
Reading is_signed and max() Correctly
The two forms in the opening lines answer different kinds of questions. numeric_limits<T>::is_signed is read as a condition: it yields a yes or no answer about the type. numeric_limits<T>::max() is a function call that returns a value: it yields the largest value of the type. Keeping this distinction clear helps you choose the right property and read the static_assert correctly.
| Expression | What it is | What it yields | How to read it in a check |
|---|---|---|---|
| numeric_limits<char>::is_signed | A property, read as a condition | A yes or no answer about char | Use it directly as the condition |
| numeric_limits<int>::max() | A function call | A value of type int | Compare it with a number, such as >= 100000 |
Two forms of the same class, read in different ways
Tracing a static_assert Through the Build
A static_assert is evaluated by the compiler, not by the running program. Follow one check through the build. The compiler reaches the static_assert line and evaluates its condition. If the condition is true, the build goes on and nothing is reported. If the condition is false, the program does not build at all, and the message string is the diagnostic you see.
Notice what this does not do. The check does not wait for the program to run, and it does not depend on which code path executes. The assumption is enforced before the program can run with a wrong type size or signedness. The message is what tells the programmer which assumption failed, which is why good messages, such as unsigned characters! and small ints!, describe the assumption itself.
Compile Time Versus Run Time
The word compile time matters. A normal check written with if runs while the program is executing, and it can only protect the code path that actually runs. A static_assert is resolved during the build, so it protects the whole program regardless of which path runs.
A Worked Check on int Range
Verifying that int holds the hundred-thousands
A program stores values up to 100000 in an int. Write a static_assert that stops the build if the platform's int cannot hold that value.
- 1Identify the property
The largest value an int can hold is given by
numeric_limits<int>::max(). This is a function call that returns a value, so it is compared with a number. - 2State the assumption as a comparison
The assumption is that the maximum is at least 100000, so the condition is
numeric_limits<int>::max()>= 100000. - 3Attach a message
The message names the failed assumption, here small ints!, so a failure is easy to interpret.
- 4Predict the outcome
Before compiling, decide whether the check should pass on your platform. Then compare that prediction with what the compiler reports.
static_assert(std::numeric_limits<int>::max() >= 100000, "small ints!"); passes silently when the condition is true on the platform, and stops the build with the message small ints! when it is false.
Where the char Assumption Diverges
The second opening line checks whether char is signed. The source gives no particular value for char on any platform, and this article does not claim one. The lesson is that a programmer who assumes char behaves one way has made a platform assumption, and that assumption is exactly where a check can diverge from expectation.
Common mistake Assuming a platform property holds everywhere
What it looks likeWriting code that relies on char holding negative values without checking it.
Why it failsThe source says such assumptions depend on the platform and are not always guaranteed.
Do this insteadWrite the assumption as a static_assert so the build reports it when it is false.
Common mistake Using if instead of static_assert for a type check
What it looks likeif (std::numeric_limits<int>::max() < 100000) { ... } placed inside a function that may never run.Why it failsA normal if is evaluated while the program runs and only protects the path that executes.
Do this insteadUse static_assert, which is evaluated by the compiler and stops the build.
Common mistake Reading max() as a yes or no answer
What it looks likeExpecting numeric_limits<int>::max() to behave like is_signed.Why it failsis_signedis a condition, whilemax()is a function call that returns a value.Do this insteadCompare max() with a number, and use is_signed directly as the condition.
Common mistake Forgetting that the check holds only for the platform it was compiled on
Write platform assumptions down as static_assert lines near the code that depends on them. A passing check holds for the platform it was compiled on, so the assumption is visible to anyone who builds the code elsewhere.
Practice Predicting a Build Result
A teammate adds static_assert(std::numeric_limits<char>::is_signed, "unsigned characters!"); to a file. Before building, predict whether the build will continue or stop on your platform, and state which message would appear if it stops. Then compare your prediction with the compiler's output.
Hints
- Ask whether
is_signedis being used as a yes or no condition. - If the condition is false, the message string is the diagnostic you see.
- The result depends on the platform, so your prediction should name the platform you are using.
Write a static_assert that verifies numeric_limits<int>::max() is at least 100000, and give it a message that names the assumption. Then explain in one sentence why the check runs before the program does.
Hints
- Use the same pattern as the worked example.
- The message should describe the assumption, not just say that something failed.
Key Takeaways
numeric_limitsis a standard library class, declared in <limits>, that provides properties of built-in types, and you query it with a type in angle brackets.numeric_limits<T>::is_signedis read as a condition, whilenumeric_limits<T>::max()is a function call that returns a value.static_assertis evaluated by the compiler: a true condition lets the build continue, and a false condition stops the build and reports its message before the program runs.- Platform assumptions about types are not always guaranteed, so a passing check holds only for the platform it was compiled on and should be written down explicitly.