Consider the following example (snippet (0)):
struct X
{
constexpr int get() const { return 0; }
};
void foo(const X& x)
{
constexpr int i = x.get();
}
int main()
{
foo(X{});
}
The above example compiles with all versions of g++
prior to g++ 10.x
, and never compiled under clang++
. The error message is:
error: 'x' is not a constant expression 8 | constexpr int i = x.get(); |
The error kind of makes sense, as x
is never a constant expression in the body of foo
, however:
X::get()
is marked constexpr
and it does not depend on the state of x
;
Changing const X&
to const X
makes the code compile with every compiler (on godbolt.org) snippet (1).
It gets even more interesting when I mark X::get()
as static
((on godbolt.org) snippet (2)). With that change, all tested versions of g++
(including trunk) compile, while clang++
still always fail to compile.
So, my questions:
Is g++ 9.x
correct in accepting snippet (0)?
Are all compilers correct in accepting snippet (1)? If so, why is the reference significant?
Are g++ 9.x
and g++ trunk
correct in accepting snippet (2)?
Deprecation Notice |
---|
This answer is now obsolete due to the changes in P2280: Using unknown pointers and references in constant expressions (proposal accepted into C++23 and applied as a defect report to C++11). GCC 9 falsely accepts the code as a bug. GCC 10-13 reject the code due to a bug fix, but do not implement P2280 yet. GCC 14 implements the DR and accepts the original code as valid. See https://godbolt.org/z/KE5G969ca. |
Is g++ 9.x correct in accepting snippet (0)?
No.
Are all compilers correct in accepting snippet (1)? If so, why is the reference significant?
Yes, they are.
A constant expression cannot use an id-expression naming a reference that doesn't have a previous constant expression initialization or began its lifetime during the constant expression evaluation. [expr.const]/2.11 (same in C++20)
The same is not true if you are naming a non-reference variable without involving any lvalue-to-rvalue conversion. x.get()
only refers to x
as lvalue and only calls a constexpr
function that doesn't actually access any member of x
, so there is no issue.
Are g++ 9.x and g++ trunk correct in accepting snippet (2)?
No, because the expression still contains the subexpression x
which violates the rule mentioned above.