3

I'm reading the "C++ 17 The Complete Guide" book by Nicolai M. Josuttis and cannot understand the following example

auto squared1 = [](auto val) constexpr { // example 1. compile-time lambda calls return val * val; }; 

and the statement to it

If (only) the lambda is constexpr it can be used at compile time, but squared1 might be initialized at run time, which means that some problems might occur if the static initialization order matters (e.g, causing the static initialization order fiasco).

The author suggests considering the following solution

constexpr auto squared = [](auto val) constexpr { return val * val; }; 

meaning that it would somehow avoid the previous problem.

I don't understand the problem with the first example regarding to the initialization order and therefore cannot understand how the author's solution improves it. Could you please explain it and give an example which demonstrates disadvantages of the first example?

3
  • auto squared1 might be computed at compile time. Or not. No guarantees. Adding constexpr provides that guarantee. Commented Sep 30, 2021 at 14:03
  • 1
    Are you familiar with static initialization order fiasco and constant initialization? Commented Sep 30, 2021 at 14:05
  • If code in a separate translation unit, while doing static initialization, uses squared1 to compute something, it could fail if the static initialization order is unfavorable. Commented Sep 30, 2021 at 14:19

1 Answer 1

3

The statement auto square1 = initialize a global variable, not even constant. This is a runtime variable that you can mutate, assign to and potentially other stuff.

You could very well initialize such variable like this:

auto returnMeALambda() { int capture = rand() % 2; return [capture](auto val) { return val * val + capture; } } auto square1 = returnMeALambda(); 

As you can see, the code of returnMeALambda is strictly runtime, so square1 is forcibly initialized at runtime.

Those variables don't have a value that it usable at compile time. The compiler might very well initialize it at runtime, even if not forced to. This has cost at runtime, and with the static initialization order fiasco, you could technically use the lambda before it is initialized, or even use another global before it is initialized:

extern int baseval; auto returnMeALambda() { int capture = baseval + rand() % 2; return [capture](auto val) { return val * val + capture; } } auto square1 = returnMeALambda(); int baseval = square1(2); 

This code will always end up being undefined behaviour since it always use an uninitialized variable, no matter the initialization order.

The solution proposed by the author is to initialize the variable as a constexpr. This do three things in this case:

  • Makes the variable const. You cannot mutate its value at runtime.
  • Constant initialization is now mandated. The value of square1 is encoded in the executable binary code.
  • Makes the variable value available at compile time. You can now use the variable in constexpr context.

The second point is the property the author seek as a solution. The variable is guaranteed it won't ever be initialized at runtime, since the value is available at compile time and guarantees constant initialization by the compiler.


Note that in C++20 you can apply the second point by itself without forcing the value to be const using constinit.

constinit int value = 9; 

Now the compiler is forced to initialize the value using constant initialization, but also the variable is mutable at runtime. This effectively solve the initialization order fiasco given you can constant initialize your variables.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.