πάντα ῥεῖ gave a good and useful answer, I would like to mention one more question, but with constexpr for .
In C ++, at the most fundamental level, all expressions have a type that can be defined statically (at compile time). Of course, there are things like RTTI and boost::any , but they are built on top of this structure, and the static type of an expression is an important concept for understanding some rules in the standard.
Suppose you can iterate over a heterogeneous container using syntax for syntax, for example:
std::tuple<int, float, std::string> my_tuple; for (const auto & x : my_tuple) { f(x); }
Here f is some overloaded function. It is clear that the intended value of this method is to call different overloads f for each of the types in the tuple. This really means that in the expression f(x) , overload resolution must be performed three times. If we play according to the current C ++ rules, the only way this might make sense is if we basically expand the loop into three different loop bodies before we try to figure out what types of expressions are.
What if the code is actually
for (const auto & x : my_tuple) { auto y = f(x); }
auto not magic, it does not mean "no type information", it means "type deduction, please, compiler". But it is clear that in the general case there really should be three different types of y .
On the other hand, there are complex problems with such a thing - in C ++, the parser must know which names are types and which names are templates in order to parse the language correctly. Is it possible to modify the parser to loop constexpr for loops before all types are resolved? I do not know, but I think it can be nontrivial. Maybe there is a better way ...
To avoid this problem, in current versions of C ++ people use the visitor pattern. The idea is that you will have an overloaded function or function object, and it will be applied to each element of the sequence. Then each overload has its own "body", so there is no ambiguity regarding the types or values of variables in them. There are libraries like boost::fusion or boost::hana that allow you to iterate over heterogeneous sequences using the given vistior - you should use your mechanism instead of the for loop.
If you can do constexpr for only using ints like
for (constexpr i = 0; i < 10; ++i) { ... }
there is the same complexity as heterogeneous for the cycle. If you can use i as a template parameter inside the body, then you can create variables that are of different types in different runs of the loop body, and then it is not clear what the static types of expressions should be.
So, I'm not sure, but I think there might be some non-trivial technical problems associated with actually adding the constexpr for function to the language. Visitor pattern / planned reflection features may turn out to be less of a headache IMO ... who knows.
Let me give you another example that I just thought about that which shows difficulty.
In regular C ++, the compiler knows the static type of each variable on the stack and therefore can calculate the layout of the stack frame for this function.
You can be sure that the address of the local variable will not change during the execution of the function. For example,
std::array<int, 3> a{{1,2,3}}; for (int i = 0; i < 3; ++i) { auto x = a[i]; int y = 15; std::cout << &y << std::endl; }
In this code, y is a local variable in the body of the for loop. It has a clearly defined address in all this function, and the address printed by the compiler will be the same every time.
What should be the behavior of similar code with constexpr for?
std::tuple<int, long double, std::string> a{}; for (int i = 0; i < 3; ++i) { auto x = std::get<i>(a); int y = 15; std::cout << &y << std::endl; }
The fact is that type x is displayed differently in each pass through the loop - since it has a different type, it can have a different size and alignment on the stack. Since y appears after it on the stack, this means that y can change its address on different runs of the loop - right?
What should be the behavior if the pointer to y taken in one pass through the loop and then dereferenced in a later pass? Should there be undefined behavior, although it would probably be legal in similar "no-constexpr for" code with std::array shown above?
Is it possible to change the address y ? Should the compiler populate the address y so that the largest of the types in the tuple can be placed before y ? Does this mean that the compiler cannot just expand the loops and start generating the code, but must expand each instance of the loop to the side, and then collect all the type information from each of the N instances, and then find a satisfactory layout?
I think you better just use the package extension, it’s much more clear how it should be implemented by the compiler, and how effective it is at compilation and runtime.