C++23 is going to introduce if consteval. Where is this going to be used and how does it differ from constexpr if?
if consteval detects if a constexpr function is called in a constant expression context. The proposal motivates its introduction for the case where one intends to call a consteval function from a constexpr function. To understand what that means we consider the following example.
Let's assume we have a consteval function f:
consteval int f( int i )
{ ... }
f can only be called in a constant expression. On the other hand a constexpr function g can be called either in a constant expression or at run time. That depends on if the arguments to g are known at compile time or not.
Now, calling f from g if g is called at compile time can be done as follows.
constexpr int g( int i )
{
if consteval { //1
return f( i );
}
else {
return fallback();
}
}
Here if consteval in line //1 triggers if g is called in a constant expression.
Note that there must be no condition in //1. Also the braces after if consteval are obligatory.
C++20 introduced is_constant_evaluated for detecting whether a function call occurs within a constant-evaluated context. Using is_constant_evaluated in our example leads to a subtle bug. I.e. exchanging //1 by if constexpr (std::is_constant_evaluated()) { results in is_constant_evaluated to always return true.
Related
I have code something like this:
template<typename ... Args>
constexpr size_t get_init_size(Args ... args) {
return sizeof...(Args);
}
template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
constexpr size_t header_lenght = get_init_size(args...);
return header_lenght;
}
constexpr auto create_ipv4_header() {
constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
return x;
}
I know it is dummy code, but I isolate it to find error.
Compiler give me error(GCC):
In instantiation of 'constexpr auto make_generic_header(Args&& ...) [with Args = {int, int, int}]':
/tmp/tmp.CaO5YHcqd8/network.h:39:43: required from here
/tmp/tmp.CaO5YHcqd8/network.h:31:22: error: 'args#0' is not a constant expression
31 | constexpr size_t header_lenght = get_init_size(args...);
| ^~~~~~~~~~~~~
I tried add qualifier const to function parameters but it same doesn't work. In theory all this function can calculate in compile time. But where is problem I can't find with my knowledge.
constexpr means different things for variables vs. functions.
For variables, it means that the variable must be compile-time. Therefore, it needs to be initialized with a constant expression.
For functions, constexpr means the function can be run at compile-time in addition to runtime using the same code inside. Therefore, everything you do inside must be applicable for a runtime call as well.
With that in mind, let's study make_generic_header:
template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
constexpr size_t header_lenght = get_init_size(args...);
return header_lenght;
}
Here, header_lenght is a constexpr variable, so must be compile-time. Therefore, get_init_size(args...) must be done at compile-time as well. However, parameters are not constant expressions for various reasons, so this won't work. If this did work, it would mean that make_generic_header, a constexpr function, is unusable at runtime¹, which doesn't fit its requirement of being usable at both compile-time and runtime.
The fix is rather simple: Use code that works in both cases:
template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
size_t header_lenght = get_init_size(args...);
return header_lenght;
}
Did you spot it? The only change is to remove constexpr from header_lenght. If this function is run at compile-time, it will still work out and the overall function call expression will be a constant expression.
¹"But it won't work in consteval either!" - True, the reasoning given in the answer is sufficient for constexpr, but I've left out the more fundamental reason since it's not relevant here.
It's not about the reference problem. And constexpr variable and constexpr function are different things. Quoted from the answer https://stackoverflow.com/a/31720324:
The reference does not have a preceding initialization from the point of view of i, though: It's a parameter. It's initialized once ByReference is called.
This is fine, since f does have preceding initialization. The initializer of f is a constant expression as well, since the implicitly declared default constructor is constexpr in this case (§12.1/5).
Hence i is initialized by a constant expression and the invocation is a constant expression itself.
And about "preceding initialization", quoted from this:
It does mean "be initialized", but it's more importantly about the visibility of a preceding initialization within the context of the expression being evaluated. In your example, in the context of evaluating func(0) the compiler has the context to see the initialization of rf with 0. However, in the context of evaluating just the expression rf within func, it doesn't see an initialization of rf. The analysis is local, as in it doesn't analyze every call-site. This leads to expression rf itself within func not being a constant expression while func(0) is a constant expression.
Corresponding to your case, the line:
constexpr size_t header_length = get_init_size(args...);
From the point of view of header_length, get_init_size(args...) is not a core constant expression, since its invoke argument is from the function's argument, and args is not a core constant expression either.
Simple modification can make your code work, you can remove the constexpr qualifier from header_length, or directly return get_init_size(args...); in make_generic_header's function body.
I hope this and this might also help you.
I rewrite the code that will be work and I think will be execute in compile time:
template<typename ... Args>
constexpr auto make_generic_header(const Args ... args) {
std::integral_constant<size_t, sizeof...(Args)> header_lenght;
return header_lenght.value;
}
constexpr auto create_ipv4_header() {
constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
return x;
}
I removed function get_init_size and used code part of template parameter(It is guaranteed that it is will be execute on compile time) and after return the number of arguments passed to function(For std::integral_constant for all object value same and know on compile time)
Let's consider the following code:
#include <type_traits>
int foo(int arg) {
if (std::is_constant_evaluated()) {
return 1;
} else {
return 0;
}
}
int main() {
const auto b = foo(0);
return b;
}
It returns 0 with both gcc and clang. I would have expected it to return 1 instead.
If foo() is made constexpr, while b is kept simply const, then it does return 1.
What am I missing here? Thanks!
std::is_constant_evaluated() returns true if and only if [meta.const.eval]:
evaluation of the call occurs within the evaluation of an expression or conversion that is manifestly constant-evaluated
This term, "manifestly constant-evaluated" (defined here), refers to contexts that have to be constant-evaluated. A call to a non-constexpr function (the nearest enclosing context here) is never constant evaluated, because it's non-constexpr, so this is straight-forwardly not "manifestly constant-evaluated."
Once we make it constexpr though, we're in this weird legacy quirk. Before C++11, which introduced constexpr, we could still do stuff like this:
template <int I> void f();
const int i = 42; // const, not constexpr
f<i>(); // ok
Basically, we have this carve out for specifically integral (and enumeration) types declared const that are initialized with a constant expression. Those still count as constant expressions.
So this:
const auto b = foo(0);
If foo(0) is an integral constant expression, then b is something that could be used as a compile time constant (and would be constant-initialized†, if it were at namespace scope). So what happens here is we do a two-step parse. We first try to evaluate foo(0) as if it were a constant expression and then, if that fails, fall back to not doing that.
In this first parse, with foo(0) evaluated as a constant, is_constant_evaluated() is (by definition) true, so we get 1. This parse succeeds, so we end up with b as a compile-time constant.
†For namespace-scope variables, constant-initialization is an important concept as well - to avoid the static initialization order fiasco. It leads to other gnarly examples (see P0595).
The important thing here is basically: is_constant_evaluated() should only be switched on to select a compile-time-safe algorithm vs a runtime algorithm, not to actually affect the semantics of the result.
You have to be a little careful with where and how you use is_constant_evaluated. There are 3 kinds of functions in C++, and is_constant_evaluated only makes sense in one of them.
// a strictly run-time function
int foo(int arg)
{
if (std::is_constant_evaluated()) // pointless: always false
// ...
}
// a strictly compile time function
consteval int foo(int arg)
{
if (std::is_constant_evaluated()) // pointless: always true
// ...
}
// both run-time and compile-time
constexpr int foo(int arg)
{
if (std::is_constant_evaluated()) // ok: depends on context in
// which `foo` is evaluated
// ...
}
Another common mistake worth pointing out is that is_constant_evaluated doesn't make any sense in an if constexpr condition either:
{
if constexpr (std::is_constant_evaluated()) // pointless: always true
// regardless of whether foo
// is run-time or compile-time
}
Please consider the following two C++14 programs:
Program 1:
struct S { constexpr int f() const { return 42; } };
S s;
int main() { constexpr int x = s.f(); return x; }
Program 2:
struct S { constexpr int f() const { return 42; } };
int g(S s) { constexpr int x = s.f(); return x; }
int main() { S s; return g(s); }
Are neither, either or both of these programs ill-formed?
Why/why not?
Both programs are well-formed. The C++14 standard requires that s.f() be a constant expression because it is being used to initialize a constexpr variable, and in fact it is a core constant expression because there's no reason for it not to be. The reasons that an expression might not be a core constant expression are listed in section 5.19 p2. In particular, it states that the evaluation of the expression would have to do one of several things, none of which are done in your examples.
This may be surprising since, in some contexts, passing a non-constant expression to a constexpr function can cause the result to be a non-constant expression even if the argument isn't used. For example:
constexpr int f(int) { return 42; }
int main()
{
int x = 5;
constexpr auto y = f(x); // ill-formed
}
However, the reason this is ill-formed is because of the lvalue-to-rvalue conversion of a non-constant expression, which is one of the things that the evaluation of the expression is not allowed to do. An lvalue-to-rvalue conversion doesn't occur in the case of calling s.f().
I can't seem to find a compelling passage or example in the standard that directly addresses the issue of calling a constexpr member function on a non-constexpr instance, but here are some that may be of help (from draft N4140):
[C++14: 7.1.5/5]:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no
diagnostic required.
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
From this I take that the program is not outright ill-formed just because a constexpr function has a possible non-constexpr path.
[C++14: 5.19]:
int x; // not constant
struct A {
constexpr A(bool b) : m(b?42:x) { }
int m;
};
constexpr int v = A(true).m; // OK: constructor call initializes
// m with the value 42
constexpr int w = A(false).m; // error: initializer for m is
// x, which is non-constant
This is somewhat closer to your example programs, here a constexpr constructor may reference a non-constexpr variable depending on the value of the argument, but there is no error if this path is not actually taken.
So I don't think either program you presented should be ill-formed, but I cannot offer convincing proof :)
This sounds like a quiz question, and not presented by a student, but the professor testing the public on stackoverflow, but let's see...
Let's start with the One Definition Rule. It's clear neither version violates that, so they both pass that part.
Then, to syntax. Neither have syntax failures, they'll both compile without issue if you don't mind the potential blend of a syntax and semantic issue.
First, the simpler semantic issue. This isn't a syntax problem, but f(), in both versions, is the member of a struct, and the function clearly makes no change to the owning struct, it's returning a constant. Although the function is declared constexpr, it is not declared as const, which means if there were some reason to call this as a runtime function, it would generate an error if that attempt were made on a const S. That affects both versions.
Now, the potentially ambiguous return g(S()); Clearly the outer g is a function call, but S may not be so clear as it would be if written return g(S{}); With {} initializing S, there would be no ambiguity in the future should struct S be expanded with an operator() (the struct nearly resembles a functor already). The constructor invoked is automatically generated now, and there is no operator() to create confusion for the compiler at this version, but modern C++14 is supposed to offer clearer alternatives to avoid the "Most Vexing Parse", which g(S()) resembles.
So, I'd have to say that based on semantic rules, they both fail (not so badly though).
Is constexpr an indicator for the compiler or does it mandate a behaviour ?
The example at hand is the following :
template<typename T>
std::size_t constexpr getID() { return typeid(T).hash_code(); }
hash_code is a runtime constant, yet this snippet would compile even though a compile time evaluation is requested with constexpr. Only after the return value is used where a compile time constant is expected, would we get noticed that this is not usable as a constexpr function.
So is constexpr a "hint" (much like the inline keyword) or "a binding request" to the compiler ?
Is constexpr a “hint” (like inline) or “a binding request” to the compiler?
It is neither. Forget about when it is evaluated. Everything (with a few minor exceptions, notably involving volatile) is evaluated whenever the compiler deems it necessary to produce the behaviour of the C++ abstract machine. There isn't much else to say about when things are evaluated.
The compiler is free to produce code that evaluates what would be constant expressions at runtime if that doesn't produce a different behaviour. It is free to produce code that evaluates things not marked constexpr at compile-time if it has the smarts.
If not about compile-time vs runtime, what is constexpr about, then?
constexpr allows things to be treated as constant expressions. Anything marked constexpr must have the possibility of producing a constant expression in some way.
In the case of functions, they can be able to produce constant expressions with some arguments but not others. But as long as there is some set of arguments that can result in a constant expression, a function can be marked constexpr. If such a set of arguments is used in a function call, that expression is a constant expression. Does that mean it is evaluated at compile-time? See above. It's evaluated when the compiler deems appropriate. The only thing it means is that you can use it in a context requiring a constant expression.
For variables, either they are constant expressions or not. They have no arguments, so if constexpr they always have to be initialised with constant expressions.
TL;DR: constexpr is about tagging things as being usable in constant expressions, not about deciding when to evaluate them.
With that out of the way, it appears your function template is ill-formed. There is no set of arguments that could result in a constant expression. The standard doesn't require a diagnostic for this, though.
From the C++11 Wiki page:
If a constexpr function or constructor is called with arguments which
aren't constant expressions, the call behaves as if the function were
not constexpr, and the resulting value is not a constant expression.
Likewise, if the expression in the return statement of a constexpr
function does not evaluate to a constant expression for a particular
invocation, the result is not a constant expression.
The constexpr specifier thus expresses the possibility to evaluate something at compile time and is subject to some restrictions when used.
For your particular snippet it seems to me that the C++11 constraint:
exactly one return statement that contains only literal values,
constexpr variables and functions
is not fulfilled, as hash_code is defined to be:
size_t hash_code() const;
In this case the standard draft n3242 says:
For a constexpr function, if no function argument values exist such
that the function invocation substitution would produce a constant
expression (5.19), the program is ill-formed; no diagnostic required.
I believe your example fits here.
constexpr functions can be used to evaluate compile time constants. So it is possible to use it like:
constexpr int func(int a) { return a+2; }
char x[func(10)];
If func is called during runtime, the compiler can evaluate this expression before if possible. But that is not a must but normally done if the input is also const.
It is also important to have constexpr constructors. This is the only chance to get non POD classes constexpr objects.
class Point
{
private:
int x;
int y;
public:
constexpr Point( int _x, int _y) : x(_x), y(_y) {}
constexpr int GetX() const { return x; }
};
constexpr Point p{1,2};
int main()
{
char i[p.GetX()];
return 0;
}
The complete answer to your question has two aspects:
A constexpr function can only be evaluated at compile-time when all
arguments can be evaluated at compile-time. It can still be used as
a normal function which is evaluated at runtime.
An constexpr variable must be initialized with a value evaluated at compile-time.
The compiler has to raise an error if it cannot do this.
You could assign the hash code to a constexpr variable and then get a compiler output:
#include <typeinfo>
#include <array>
template<typename T>
std::size_t constexpr getID() {
return []() {constexpr size_t id = typeid(T).hash_code(); return id;}(); }
int main() {
// both statement generate compiler errors
//std::array<int, typeid(int).hash_code()> a;
//constexpr size_t y = typeid(int).hash_code();
size_t x = getID<int>();
}
Can someone please explain why the marked line below compiles fine:
template<typename T, int N>
constexpr
int get_size(T (&)[N])
{
return N;
}
int main()
{
int xs[10];
constexpr int y = get_size(xs); // HERE.
static_assert(10 == y, "wrong size");
}
Intuitively to me, get_size(xs) isn't a constant expression because xs itself isn't so I don't understand why it works.
After the template function is instantiated your program becomes equivalent to the following:
constexpr
int get_size(int (&)[10])
{
return 10;
}
int main()
{
int xs[10];
constexpr int y = get_size(xs); // HERE.
static_assert(10 == y, "wrong size");
}
Then after function invocation substitution it becomes equivalent to the following:
int main()
{
int xs[10];
constexpr int y = 10; // HERE.
static_assert(10 == y, "wrong size");
}
Function invocation substitution is described under 7.1.5 [dcl.constexpr]/5. Essentially parameters are replaces as if copy-initialized and then subsituted for occurences in the return expression. The return expression then likewise as-if copy-initializes the return value. The resulting expression then becomes the expression that is subsituted for the function call. Only after this is the expression considered if it satisfies the constraints on constant expressions placed by the context. (Note, a quality compiler can of course determine that the constexpr function can never be used successfully after any such operation, and can fail after encounting the function definition, but it doesn't have to)
Also note, just to confuse you this concept is removed in C++14 and replaced with a different concept for how constexpr functions are evaluated. Among other things you will be able to use if statements, for statements and local variables of literal type within constexpr functions.
Your question and the comment:
I guess I'm confused why an automatic variable whose address isn't known can be passed by reference to a function used in a constant expression
When the compiler sees get_size(xs), it has already parsed the previous line which is int xs[10]; and thus knows the type and size of xs. There is nothing going to change at runtime, as far the type and the size is concerned — and these two are the information required by the compile in order to instantiate the function template, so it doesn't face any problem instantiating the function template, which in this case behaves as constexpr because everything is known at compile-time, which is why the static_assert doesn't fail.