This code compiles just fine on all big 4 compilers, even on -pedantic
struct S
{
constexpr S(int a) {}
};
constexpr int f(S a)
{
return 1;
}
int main()
{
int a = 0;
S s(a);
constexpr int b = f(s);
}
However, this shouldn't be so according to the standard... right? Firstly, s
wouldn't be usable in constant expressions [expr.const]/3, because it fails to meet the criteria of being either constexpr
, or, const
and of enum or integral type.
Secondly, it is not constant-initialized [expr.const]/2 because the full expression of the initialization would not be a constant expression [expr.const]/10 due to a lvalue-to-rvalue conversion being performed on a variable (a
) that is not usable in constant expressions when initializing the parameter of the constructor.
Are all these compilers just eliding the initialization of the parameter of the constructor because it has no side-effects, and is it standard conforming (I'm 99% sure it isn't, as the only way for it to so would be to make s
constexpr
, and to pass it either a const int
or a int
that is declared constexpr
)?
A constant expression is an expression that can be evaluated at compile time. Constants of integral or enumerated type are required in several different situations, such as array bounds, enumerator values, and case labels. Null pointer constants are a special case of integral constants.
A constant expression is an expression that contains only constants. A constant expression can be evaluated during compilation rather than at run time, and can be used in any place that a constant can occur.
A constant must be initialized. This error has the following causes and solutions: You tried to initialize a constant with a variable, an instance of a user-defined type, an object, or the return value of a function call.
I believe the magician's trick here is the copy c'tor of S
. You omitted it, so a defaulted one is generated for you here. Now it's a constexpr
function too.
[class.copy.ctor] (emphasis mine)
12 A copy/move constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]), when it is needed for constant evaluation ([expr.const]), or when it is explicitly defaulted after its first declaration. [ Note: The copy/move constructor is implicitly defined even if the implementation elided its odr-use ([basic.def.odr], [class.temporary]). — end note ] If the implicitly-defined constructor would satisfy the requirements of a constexpr constructor ([dcl.constexpr]), the implicitly-defined constructor is constexpr.
Does the evaluation of the copy c'tor run afoul of any of the points in [expr.const]/4? It does not. It doesn't perform an lvalue to rvalue conversion on any of the argument's members (there are none to perform the conversion on). It doesn't use its reference parameter in any way that will require said reference to be usable in a constant expression. So we indeed get a valid constant expression, albeit a non-intuitive one.
We can verify the above by just adding a member to S
.
struct S
{
int a = 1;
constexpr S(int a) {}
};
Now the copy c'tor is trying to access an object that is not usable in a constant expression as part of its evaluation (via said reference). So indeed, compilers will complain.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With