Concepts / Compile-Time Checks with static_assert

Compile-Time Checks with static_assert

A static_assert is evaluated by the compiler, not by the running program.

  • Programming

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.

cpp
Notes on the code
  • 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?

  • Yes, both always pass

  • No, the results depend on the platform

  • Only the first one can fail

Reveal answer
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

Definition

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.

numeric_limits<T>lookup by typeis_signedyes or no answermax()returns a valuebytes in inta property of the typescope operatorscope operatorscope operator
For a given type T, which property does numeric_limits<T> return, and how is each one looked up?

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.

ExpressionWhat it isWhat it yieldsHow to read it in a check
numeric_limits<char>::is_signedA property, read as a conditionA yes or no answer about charUse it directly as the condition
numeric_limits<int>::max()A function callA value of type intCompare 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.

static_assertreachedcondition true?build continuesbuild stops, messageshownevaluateyesno
When the condition is false, what happens to the build, and when it is true, what happens instead?

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.

Source codecontains static_assertCompiler evaluateschecksnumeric_limits queriesresolvedExecutableonly if all checks passProgram runsbuildchecks passexecute
At which stage of building a program are numeric_limits queries and the static_assert check evaluated, and what reaches the final executable?

A Worked Check on int Range

Worked example

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.

  1. 1
    Identify 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.

  2. 2
    State the assumption as a comparison

    The assumption is that the maximum is at least 100000, so the condition is numeric_limits<int>::max() >= 100000.

  3. 3
    Attach a message

    The message names the failed assumption, here small ints!, so a failure is easy to interpret.

  4. 4
    Predict the outcome

    Before compiling, decide whether the check should pass on your platform. Then compare that prediction with what the compiler reports.

Result

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 like

    Writing code that relies on char holding negative values without checking it.

    Why it fails

    The source says such assumptions depend on the platform and are not always guaranteed.

    Do this instead

    Write 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 like
    if (std::numeric_limits<int>::max() < 100000) { ... } placed inside a function that may never run.
    Why it fails

    A normal if is evaluated while the program runs and only protects the path that executes.

    Do this instead

    Use 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 like
    Expecting numeric_limits<int>::max() to behave like is_signed.
    Why it fails

    is_signed is a condition, while max() is a function call that returns a value.

    Do this instead

    Compare 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

Best practice

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

MEDIUM

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_signed is 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.
EASY

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_limits is 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_signed is read as a condition, while numeric_limits<T>::max() is a function call that returns a value.
  • static_assert is 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.

Related concepts

More concepts starting with C