In C++17, this code is illegal:
constexpr int foo(int i) {
return std::integral_constant<int, i>::value;
}
That's because even if foo can be evaluated at compile-time, the compiler still needs to produce the instructions to execute it at runtime, thus making the template instantiation impossible.
In C++20 we will have consteval functions, which are required to be evaluated at compile-time, so the runtime constraint should be removed. Does it mean this code will be legal?
consteval int foo(int i) {
return std::integral_constant<int, i>::value;
}
No.
Whatever changes the paper will entail, which is little at this point, it cannot change the fact that a non-template function definition is only typed once. Moreover, if your proposed code would be legal, we could presumably find a way to declare a variable of type std::integral_constant<int, i>, which feels very prohibitive in terms of the ODR.
The paper also indicates that parameters are not intended to be treated as core constant expressions in one of its examples;
consteval int sqrsqr(int n) {
return sqr(sqr(n)); // Not a constant-expression at this point,
} // but that's okay.
In short, function parameters will never be constant expressions, due to possible typing discrepancy.
Does it mean this code will be legal?
consteval int foo(int i) {
return std::integral_constant<int, i>::value;
}
No. This is still ill-formed. While consteval requires the call itself to be a constant expression, so you know that the argument that produces i must be a constant expression, foo itself is still not a template. Template?
A slight variation in your example might make this more obvious:
consteval auto foo(int i) {
return std::integral_constant<int, i>();
}
Were this to be valid, foo(1) and foo(2) would... return different types. This is an entirely different language feature (constexpr function parameters) - because in order for this to work, such functions would really need to behave like templates.
It may seem a little unintuitive. After all, if the argument that produced i was a constant expression, surely i should be usable as one as well? But it's still not - there are no additional exceptions in [expr.const] that permit parameters for immediate functions. An immediate function is still just a function, and its parameters still aren't constant expressions -- in the same way that a normal constexpr function's parameters aren't constant expressions.
Of course with int, we can just rewrite the function to lift the function parameter into a template parameter:
template <int i>
consteval int foo() {
return std::integral_constant<int, i>::value;
}
And C++20 gives us class types as non-type template parameters, so we can actually do this for many more types than we could before. But there are still plenty of types that we could use as a parameter to an immediate function that we cannot use as a template parameter - so this won't always work (e.g. std::optional or, more excitingly in C++20, std::string).
It would seem that this will not be legal in C++20. A good explanation for why this would be problematic to support has already been given in the answers by #Barry and #Columbo (it doesn't really work with the type system). I'll just add what I believe to be the relevant quotes from the standard here that actually make this illegal.
Based on [temp.arg.nontype]/2
A template-argument for a non-type template-parameter shall be a converted constant expression […]
A converted constant expression is a constant expression that is implicitly converted to a particular type [expr.const]/7 (here, the type of the template parameter). So your question boils down to the question of whether a variable inside a consteval function is a constant expression. Based on [expr.const]/8
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints: […]
The expression i is a glvalue id-expression that is a core constant expression (because its evaluation does not do any of the things listed in [expr.const]/4). However, the entity this core constant expression refers to is not a permitted result of a constant expression [expr.const]/8:
An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.
The object in question is neither of static storage duration nor is it a temporary object…
Related
Recently I was surprised that the following code compiles in clang, gcc and msvc too (at least with their current versions).
struct A {
static const int value = 42;
};
constexpr int f(A a) { return a.value; }
void g() {
A a; // Intentionally non-constexpr.
constexpr int kInt = f(a);
}
My understanding was that the call to f is not constexpr because the argument i isn't, but it seems I am wrong. Is this a proper standard-supported code or some kind of compiler extension?
As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.
There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.
The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).
Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.
Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.
Note that, as mentioned above, the rules are stricter if you use references, e.g.
A a;
A& ar = a;
constexpr int kInt = f(ar);
will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)
In C++17, this code is illegal:
constexpr int foo(int i) {
return std::integral_constant<int, i>::value;
}
That's because even if foo can be evaluated at compile-time, the compiler still needs to produce the instructions to execute it at runtime, thus making the template instantiation impossible.
In C++20 we will have consteval functions, which are required to be evaluated at compile-time, so the runtime constraint should be removed. Does it mean this code will be legal?
consteval int foo(int i) {
return std::integral_constant<int, i>::value;
}
No.
Whatever changes the paper will entail, which is little at this point, it cannot change the fact that a non-template function definition is only typed once. Moreover, if your proposed code would be legal, we could presumably find a way to declare a variable of type std::integral_constant<int, i>, which feels very prohibitive in terms of the ODR.
The paper also indicates that parameters are not intended to be treated as core constant expressions in one of its examples;
consteval int sqrsqr(int n) {
return sqr(sqr(n)); // Not a constant-expression at this point,
} // but that's okay.
In short, function parameters will never be constant expressions, due to possible typing discrepancy.
Does it mean this code will be legal?
consteval int foo(int i) {
return std::integral_constant<int, i>::value;
}
No. This is still ill-formed. While consteval requires the call itself to be a constant expression, so you know that the argument that produces i must be a constant expression, foo itself is still not a template. Template?
A slight variation in your example might make this more obvious:
consteval auto foo(int i) {
return std::integral_constant<int, i>();
}
Were this to be valid, foo(1) and foo(2) would... return different types. This is an entirely different language feature (constexpr function parameters) - because in order for this to work, such functions would really need to behave like templates.
It may seem a little unintuitive. After all, if the argument that produced i was a constant expression, surely i should be usable as one as well? But it's still not - there are no additional exceptions in [expr.const] that permit parameters for immediate functions. An immediate function is still just a function, and its parameters still aren't constant expressions -- in the same way that a normal constexpr function's parameters aren't constant expressions.
Of course with int, we can just rewrite the function to lift the function parameter into a template parameter:
template <int i>
consteval int foo() {
return std::integral_constant<int, i>::value;
}
And C++20 gives us class types as non-type template parameters, so we can actually do this for many more types than we could before. But there are still plenty of types that we could use as a parameter to an immediate function that we cannot use as a template parameter - so this won't always work (e.g. std::optional or, more excitingly in C++20, std::string).
It would seem that this will not be legal in C++20. A good explanation for why this would be problematic to support has already been given in the answers by #Barry and #Columbo (it doesn't really work with the type system). I'll just add what I believe to be the relevant quotes from the standard here that actually make this illegal.
Based on [temp.arg.nontype]/2
A template-argument for a non-type template-parameter shall be a converted constant expression […]
A converted constant expression is a constant expression that is implicitly converted to a particular type [expr.const]/7 (here, the type of the template parameter). So your question boils down to the question of whether a variable inside a consteval function is a constant expression. Based on [expr.const]/8
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints: […]
The expression i is a glvalue id-expression that is a core constant expression (because its evaluation does not do any of the things listed in [expr.const]/4). However, the entity this core constant expression refers to is not a permitted result of a constant expression [expr.const]/8:
An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.
The object in question is neither of static storage duration nor is it a temporary object…
In C++17, this code is illegal:
constexpr int foo(int i) {
return std::integral_constant<int, i>::value;
}
That's because even if foo can be evaluated at compile-time, the compiler still needs to produce the instructions to execute it at runtime, thus making the template instantiation impossible.
In C++20 we will have consteval functions, which are required to be evaluated at compile-time, so the runtime constraint should be removed. Does it mean this code will be legal?
consteval int foo(int i) {
return std::integral_constant<int, i>::value;
}
No.
Whatever changes the paper will entail, which is little at this point, it cannot change the fact that a non-template function definition is only typed once. Moreover, if your proposed code would be legal, we could presumably find a way to declare a variable of type std::integral_constant<int, i>, which feels very prohibitive in terms of the ODR.
The paper also indicates that parameters are not intended to be treated as core constant expressions in one of its examples;
consteval int sqrsqr(int n) {
return sqr(sqr(n)); // Not a constant-expression at this point,
} // but that's okay.
In short, function parameters will never be constant expressions, due to possible typing discrepancy.
Does it mean this code will be legal?
consteval int foo(int i) {
return std::integral_constant<int, i>::value;
}
No. This is still ill-formed. While consteval requires the call itself to be a constant expression, so you know that the argument that produces i must be a constant expression, foo itself is still not a template. Template?
A slight variation in your example might make this more obvious:
consteval auto foo(int i) {
return std::integral_constant<int, i>();
}
Were this to be valid, foo(1) and foo(2) would... return different types. This is an entirely different language feature (constexpr function parameters) - because in order for this to work, such functions would really need to behave like templates.
It may seem a little unintuitive. After all, if the argument that produced i was a constant expression, surely i should be usable as one as well? But it's still not - there are no additional exceptions in [expr.const] that permit parameters for immediate functions. An immediate function is still just a function, and its parameters still aren't constant expressions -- in the same way that a normal constexpr function's parameters aren't constant expressions.
Of course with int, we can just rewrite the function to lift the function parameter into a template parameter:
template <int i>
consteval int foo() {
return std::integral_constant<int, i>::value;
}
And C++20 gives us class types as non-type template parameters, so we can actually do this for many more types than we could before. But there are still plenty of types that we could use as a parameter to an immediate function that we cannot use as a template parameter - so this won't always work (e.g. std::optional or, more excitingly in C++20, std::string).
It would seem that this will not be legal in C++20. A good explanation for why this would be problematic to support has already been given in the answers by #Barry and #Columbo (it doesn't really work with the type system). I'll just add what I believe to be the relevant quotes from the standard here that actually make this illegal.
Based on [temp.arg.nontype]/2
A template-argument for a non-type template-parameter shall be a converted constant expression […]
A converted constant expression is a constant expression that is implicitly converted to a particular type [expr.const]/7 (here, the type of the template parameter). So your question boils down to the question of whether a variable inside a consteval function is a constant expression. Based on [expr.const]/8
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints: […]
The expression i is a glvalue id-expression that is a core constant expression (because its evaluation does not do any of the things listed in [expr.const]/4). However, the entity this core constant expression refers to is not a permitted result of a constant expression [expr.const]/8:
An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.
The object in question is neither of static storage duration nor is it a temporary object…
I wonder how a compiler treats lambda functions as opposed to regular functions. Even excluding the capture-list, as I think it's called, it seems to behave slightly differently.
For example, as used in my last post, Trying to pass a constexpr lambda and use it to explicitly specify returning type, I used a constexpr lambda and passed it as a regular function parameter. I quote a part of the answer.
Parameters to constexpr functions are not themselves constexpr objects - so you cannot use them in constant expressions.
template <typename Lambda_T>
constexpr static auto foo(Lambda_T l) {
return std::array<event, (l())>{};
}
// Compiles with GCC (C++17), though ill-formed (according to the answer of my last post)
Though, what catches my eye is that this does compile passing a constexpr lambda, but does not compile passing a constexpr regular (global) function. What causes this difference?
And are there other differences of behaviour of the compiler between regular functions and lambda-functions?
Edit: Example of Lambda-implementation
foo([](){ return 4; }); // C++17 Lambda's are implicitly constexpr
The lambda is basically a wrapper for the value in this case.
Edit: Global function
Whenever a global function is passed, the compiler will complain - as opposed to using a lambda - that this function, regardless of whether it is defined constexpr or not, cannot be used in a constant condition:
prog.cc:8:20: error: 'l' is not a constant expression
Removing some extraneous stuff from your snippet, we get
template<typename T>
constexpr void foo(T t)
{
constexpr int i = t();
}
constexpr int f() { return 42; }
auto l = []{ return 42; }
Remarks:
You are attempting to use t() as a constexpr within foo. foo could in fact be a normal function and still behave the same.
A lambda isn't a function. It is an anonymous struct with an operator().
struct L { constexpr int operator()() const { return 42; } };
T is deduced as a type equivalent to L in the call foo(l).
T is deduced as int(*)() in the call foo(f).
In both cases, t isn't a constexpr within foo.
From [expr.const]
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions: [...]
an lvalue-to-rvalue conversion unless [...]
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
Calling through a int(*)() requires a lvalue-to-rvalue conversion. t isn't an object defined with constexpr, neither did it begin its lifetime within the evaluation of t(). Therefore t() isn't a constexpr in foo(f).
Calling operator() doesn't require a lvalue-to-rvalue conversion. Since there is no member access, it is simply a constexpr function call. Therefore t() is a constexpr in foo(l).
There is one more step from core constant expressions to constant expressions, but isn't important for the discussion here.
According to this answer apparently there is no good reason why structured bindings are not allowed to be constexpr, yet the standard still forbids it. In this case, however, shouldn't the use of the structured bindings inside the constexpr function also be prohibited? Consider a simple snippet:
#include <utility>
constexpr int foo(std::pair<int, int> p) {
auto [a, b] = p;
return a;
}
int main() {
constexpr int a = foo({1, 2});
static_assert(a == 1);
}
Both gcc and clang does not cause trouble compiling the code. Is the code ill-formed either way or is this one actually allowed?
In the case of function declaration, the constexpr specifier is an assertion made to the compiler that the function being declared may be evaluated in a constant expression, i.e. an expression that can be evaluated at compile-time. Nevertheless the object initialization inside a declaration does not need to have constexpr inside its declaration specifier to be a constant expression.
Shorter: constexpr function may imply constant expression but constant expression initialization does not need that the associated declaration has a constexpr specifier.
You can check this in the C++ standard [dcl.constexpr]:
A call to a constexpr function produces the same result as a call to an equivalent non-constexpr function in
all respects except that
— a call to a constexpr function can appear in a constant expression[...]
This is the evaluation of an expression that determines if an expression is a
constant expression [expr.const]:
An expression e is a core constant expression unless the evaluation of e [...] would evaluate one of the following expression[...]
A declaration is not an expression, so an initialization of an object being declared is a constant expression irrespective of the presence or not of a constexpr specifier in the declaration.
Finally, in [dcl.constexpr], it is specified that a constexpr function must be such that there exist parameters for which its body can be evaluated as a constant expression:
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument
values exist such that an invocation of the function or constructor could be an evaluated subexpression of
a core constant expression (8.20), or, for a constructor, a constant initializer for some object (6.6.2), the
program is ill-formed, no diagnostic required.
When you declare constexpr int a the compiler expects a to be inialized by a constant expression and the expression foo({1,2}) is a constant expression, so your code is well formed.
PS: Nevertheless, declaration specifiers (static, thread_local=>static) in the the declaration of function local variable implies that the function cannot be declared constexpr.
There are several requirements that a constexpr function must meet. There are some requirements for the body of a constexpr function, and the shown code does not appear to violate any of them. The key point is that there is no requirement that every statement in a function must be a constexpr. The only interesting requirement in question here, is this one:
there exists at least one set of argument values such that an
invocation of the function could be an evaluated subexpression of a
core constant expression (for constructors, use in a constant
initializer is sufficient) (since C++14). No diagnostic is required
for a violation of this bullet.
Note the last sentence. The compiler may, but is not required to, throw a red flag.
The key requirement is merely that there is some assortment of parameter values to the function that results in a constant result from the function (and the function body meets the listed requirements). For example, the function might use a structured binding conditionally; but for some set of parameter values do something else, producing a constant result. This would tick this checkbox for a constexpr function.
But, despite the sophistication of modern C++ compilers, they may not necessarily be capable of reaching this determination in every possible instance, so, in practice, it would be hard to enforce such a requirement, hence the compilers are permitted to just take this for granted.