I wrote the following program where if a class have a non-static data member then when an object of that type is passed as argument by value, then it can't be used in a constexpr context. After searching for the issue on the web, I came across this gcc bug where some user said something like when the class is empty, then no lvalue to rvalue transformation is applied which is why the A in the below example works but not B. But then other user is saying that this isn't the case.
I want to know how exactly the standard allows the empty class case to work but not the non-empty class case. Demo
struct A {
static constexpr int value = 42;
};
struct B
{
static constexpr int value = 42;
int mem{};
};
constexpr int f(A a) { return A::value; }
constexpr int f(B b) { return B::value; }
int main() {
A a;
constexpr int aconst = f(a); //works in all compilers
B b;
constexpr int bconst = f(b); //fails in all compilers, why?
}
Gcc says:
<source>: In function 'int main()':
<source>:18:31: error: the value of 'b' is not usable in a constant expression
18 | constexpr int bconst = f(b); //fails in all compilers, why?
| ^
<source>:17:7: note: 'b' was not declared 'constexpr'
17 | B b;
|
Basically I want to know if the claim that lvalue to rvalue transformation is bypassed in case of empty class case is true or not. And if it is, where/how exactly according to the standard.
[dcl.init.general]/14
The semantics of the copy-initializations in the two function calls in the question are governed by [dcl.init.general]/16.6.2:
The initialization of the parameter
afrom the argumenta, or the parameterbfrom the argumentb, is by constructor. In these cases the copy constructors are used (I hope this is obvious enough that I don't need to explain it).Since
AandBdon't have user-declared copy constructors, [class.copy.ctor]/6 applies:The semantics of an implicitly declared copy constructor are given by [class.copy.ctor]/14.
Since
Ahas neither bases nor non-static data members, its copy constructor does nothing.Bon the other hand has a single non-static data member of typeint, so according to the above, its implicit copy constructor direct-initializesmemfrom thememmember of the object being copied, which is a const lvalue of typeint(because the parameter to the copy constructor has typeconst B&).The semantics of a direct-initialization of an
intfrom a const lvalue of typeintare given by [dcl.init.general]/16.9:The destination type is
int, so we must form a standard conversion sequence from an lvalue ofconst intto a prvalue ofint. The standard conversion that can accomplish this is the lvalue-to-rvalue conversion. See [conv.lval]/1 (footnotes omitted):Lvalue-to-rvalue conversions are applied when the rules of the language either explicitly require them to be performed, or when the rules of the language call for a standard conversion sequence to be performed from some source type to some destination type and the lvalue-to-rvalue conversion ends up being a necessary step in that standard conversion sequence. The copy-initialization of an empty class type such as
Afrom the same type is not one of these situations since, based on the above, it is specified to have the behaviour of calling a constructor (not performing a standard conversion) and there is no other rule that demands the lvalue-to-rvalue conversion in such a case. However, copying a glvalue of a scalar type always entails an lvalue-to-rvalue conversion.