How does a compiler treat lambdas differently than regular functions? - c++

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.

Related

Consteval constructor and member function calls in constexpr functions

struct A {
int i;
consteval A() { i = 2; };
consteval void f() { i = 3; }
};
constexpr bool g() {
A a;
a.f();
return true;
}
int main() {
static_assert(g());
}
https://godbolt.org/z/hafcab7Ga
The program is rejected by all of GCC, Clang, MSVC and ICC and replacing constexpr on g by consteval results in all four accepting it.
However, when removing the call a.f();, still with constexpr on g, only ICC still rejects the code. The other three now accept it.
I don't understand why this is the case. My understanding is that without consteval on g, the expression a.f() is not in an immediate function context, which will cause the member function call itself to be evaluated as separate constant expression, which then cannot modify the i member because the member's lifetime didn't begin during evaluation of that constant expression.
But why can the constructor perform the same operation on the same object, in the same context? Is a's lifetime considered to have begun during the evaluation of the consteval constructor?
Also note that the presence of the static_assert doesn't affect these results. Removing in addition constexpr completely from g then also doesn't change anything about the compiler behavior.
As noted by #Enlico, even replacing both A a; and a.f(); by A{}.f(); with constexpr on g results in all compilers except ICC accepting the code, although by my understanding, this expression should result in evaluation of two separate constant expressions for the immediate constructor invocation and for the immediate member function invocation. I think the latter call should behave exactly as a.f();, making this even more confusing.
(After reading #Barry's answer, I realize now that the last sentence didn't make any sense. Correction: A{} would be one constant expression for the constructor immediate invocation and A{}.f() as a whole would be the second constant expression for the member function immediate invocation. This is clearly different from the expression a.f().)
The rule is, from [expr.const]/13:
An expression or conversion is in an immediate function context if it is potentially evaluated and its innermost non-block scope is a function parameter scope of an immediate function. An expression or conversion is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.
Where, an immediate function is simply the term for (from [dcl.constexpr]/2):
A function or constructor declared with the consteval specifier is called an immediate function.
From the example:
struct A {
int i;
consteval A() { i = 2; };
consteval void f() { i = 3; }
};
constexpr bool g() {
A a;
a.f();
return true;
}
The call a.f() is an immediate invocation (we're calling an immediate function and we're not in an immediate function context, g is constexpr not consteval), so it must be a constant expression.
It, by itself, must be a constant expression. Not the whole invocation of g(), just a.f().
Is it? No. a.f() mutates a by writing into a.i, which violates [expr.const]/5.16. One of the restrictions on being a constant expression is that you cannot have:
a modification of an object ([expr.ass], [expr.post.incr], [expr.pre.incr]) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
Our object, a.i, didn't begin its lifetime within the evaluation of this expression. Hence, a.f() isn't a constant expression so all the compilers are correct to reject.
It was noted that A().f(); would be fine because now we hit the exception there - A() began its lifetime during the evaluation of this expression, so A().i did as well, hence assigning to it is fine.
You can think of this as meaning that A() is "known" to the constant evaluator, which means that doing A().i = 3; is totally fine. Meanwhile, a was unknown - so we can't do a.i = 3; because we don't know what a is.
If g() were a consteval function, the a.f() would no longer be an immediate invocation, and thus we would no longer require that it be a constant expression in of itself. The only requirement now is that g() is a constant expression.
And, when evaluating g() as a constant expression, the declaration of A a; is now within the evaluation of the expression, so a.f() does not prevent g() from being a constant expression.
The difference in rules arises because consteval functions need to be only invoked during compile time, and constexpr functions can still be invoked at runtime.

Nested call of consteval functions with a reference argument

The following program
template<class T>
consteval auto foo(const T&) {
return 0;
}
template<class T>
consteval auto bar(const T& t) {
auto n = foo(t);
return n;
}
int main() {
static_assert(foo("abc") == 0);
static_assert(bar("abc") == 0);
}
is built fine in GCC, but Clang rejects it with the messages:
error: call to consteval function 'foo<char[4]>' is not a constant expression
note: in instantiation of function template specialization 'bar<char[4]>' requested here
static_assert(bar("abc") == 0);
note: function parameter 't' with unknown value cannot be used in a constant expression
auto n = foo(t);
Demo: https://gcc.godbolt.org/z/M6GPnYdqb
Is it some bug in Clang?
This is a clang bug. gcc and msvc are correct to accept it.
There are two relevant rules in question:
All immediate invocations must be constant expressions. This comes from [expr.const]/13:
An expression or conversion is in an immediate function context if it is potentially evaluated and either:
its innermost enclosing non-block scope is a function parameter scope of an immediate function, or
its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).
An expression or conversion is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.
And touching an unknown reference is not allowed in constant expressions (this is [expr.const]/5.13):
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following: [...]
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is usable in constant expressions or
its lifetime began within the evaluation of E;
For more on this latter rule, see my post on the the constexpr array size problem and my proposal to resolve this (hopefully for C++23).
Okay, back to the problem. foo is obviously fine, it doesn't do anything.
In bar, we call foo(t). This is not a constant expression (because t is an unknown reference), but we are in an immediate function context (because bar is consteval), so it doesn't matter that foo(t) is not a constant expression. All that matters is that bar("abc") is a constant expression (since that is an immediate invocation), and there's no rule we're violating there. It is pretty subtle, but the reference t here does have its lifetime begin within the evaluation of E -- since E here is the call bar("abc"), not the call foo(t).
If you mark bar constexpr instead of consteval, then the foo(t) call inside of it becomes an immediate invocation, and now the fact that it is not a constant expression is relevant. All three compilers correctly reject in this case.

Parameters of an immediate function [duplicate]

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…

evaluating constexpr inside template brackets [duplicate]

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…

Will consteval functions allow template parameters dependent on function arguments?

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…