Updated with more exact references to the standard:
The pieces I found relevant (links are from the N4868 draft found here):
- An immediate invocation is a full-expression [expr.const]
- "Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. ... The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression." [class.temporary]
- "The argument list is the expression-list in the call augmented by the addition of the left operand of the . operator in the normalized member function call as the implied object argument ([over.match.funcs])." [over.call.func]
- "A constant expression is either a glvalue core constant expression that ..., or a prvalue core constant expression whose ..." [expr.const]
- "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 invocation of a non-constexpr function;" [expr.const]
- "An immediate invocation shall be a constant expression." [expr.const]
- "An object or reference is usable in constant expressions if it is ... a temporary object of non-volatile const-qualified literal type whose lifetime is extended ([class.temporary]) to that of a variable that is usable in constant expressions," [expr.const]
- "A type is a literal type if it is: ... a possibly cv-qualified class type that has all of the following properties: it has a constexpr destructor ([dcl.constexpr])," [basic.types]
Consider the following example:
struct A {
~A() {} // not constexpr
consteval int f() { return 1; }
};
template<class T>
consteval int f(T&& a) { return sizeof(a); }
consteval int f(int x) { return x; }
void g() {}
int main() {
A a;
f(a); // ok
a.f(); // ok
f(a.f()); // ok
f(sizeof(A{})); // ok
f(A{}); // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
A{}.f(); // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
f((A{},2)); // not ok TYPE 1 (clang, msvc) or TYPE 2 (gcc)
f((g(),2)); // not ok TYPE 1 (clang, gcc, icc, msvc)
}
Error diagnostics are about violating that immediate invocations should be constant expressions.
// msvc:
error C7595: 'f' ((or 'A::f')): call to immediate function is not a constant expression
// icc:
call to consteval function "f(T&&) [with T=A]" ((or "A::f" or "f(int)")) did not produce a valid constant expression
// clang:
error: call to consteval function 'f<A>' ((or 'A::f' or 'f')) is not a constant expression
Note that gcc does not mention the violation of this consteval/immediate function specific rule explicitly.
For the temporaries we receive two types of diagnostics from different compilers.
Some see the problem in calling a non-constexpr destructor or function in a constant (full-)expression. TYPE 1:
// msvc:
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of 'A::~A' ((or 'g'))
// icc:
note: cannot call non-constexpr function "g"
// gcc:
error: call to non-'constexpr' function 'void g()'
// clang:
note: non-constexpr function '~A' ((or 'g')) cannot be used in a constant expression
Others (except for icc, which is silent about it) highlight that non-literal type temporaries cannot be present in constant expressions. TYPE 2:
// gcc:
error: temporary of non-literal type 'A' in a constant expression
note: 'A' is not literal because:
note: 'A' does not have 'constexpr' destructor
// clang:
note: non-literal type 'A' cannot be used in a constant expression
I think for consteval consideration A{}.f()
is equivalent to the f(A{})
case because of the implicit object parameter of A::f
.
The surprising observation from Fedor that icc compiles A{A{}}.f()
is true even if A::A(const A&)
is implemented to call e.g. printf
. The code
compiles, but outputs nothing. I consider that a bug.
Interestingly icc generates an error for the semantically very similar f(A{A{}})
variant.
My original post for reference (helps understanding some of the comments) :
For me the output diagnostics make sense.
My mental model about immediate invocations is this: you are allowed to use an immediate function only within immediate contexts.
An expression that contains anything else than constexpr operations is not an immediate context.
In your example the expression is not only an invocation of the constexpr constructor, but because the temporary is part of the expression, its destruction should also happen as part of the evaluation of the expression. Therefore your expression is no longer an immediate context.
I was playing around just calling the constructor with placement new to avoid the dtor call being part of the expression, but placement new itself is not considered constexpr either. Which is, I think, conceptually best explained by pointers should not present in immediate contexts at all.
If you remove ctor/dtor from the expression:
A a;
a.f();
then it compiles fine.
An interesting bug in ICC that it fails to compile A{}.f()
even with a constexpr
dtor, and you cannot convince it no matter how trivial definition your f
has:
error: call to consteval function "A::f" did not produce a valid constant expression
A{}.f();
^
while it compiles the simple a.f()
variant listed above without any complaint.
consteval
is if you only intend for that object to ever be seen within constant expressions. But you seem to want to keep using them like they're regular objects that you can create anywhere. – Breakwaterstd::format
uses a type with only aconsteval
constructor outside constant expressions to implement the compile-time format string check. I am wondering what exactly the limits for this are. But the question here is more specifically about the interpretation of the cited paragraph of the standard. I could have probably constructed a similar example with onlyconstexpr
, e.g. godbolt.org/z/YoT93nvod. I will make it clearer in the question. – Maiceconsteval
I wasn't able to construct an example in which I can callf
on theA
object without the destructor call being part of the constant expression. The restriction on literal types forconstexpr
variables seems to make that impossible. So I couldn't get to form an example in which the cited paragraph matters. – Maice~A()
asconstexpr
then all error are gone: gcc.godbolt.org/z/z8W51efYo – FernandaA
is a literal type. But non-literal types have restrictions in constant expression contexts, which is what I want to understand better. I don't know of a use case where this is required (and there probably is none), but I think that the compilers are wrong to reject the code. I'd just like to know if I am understanding the standard correctly. – MaiceA{}.f();
is rejected by all;A{A{}}.f();
is accepted by ICC. Demo: godbolt.org/z/GfYreYK9M – Fernanda