Invoking `constexpr` member function through reference - clang vs gcc - c++

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();
|
live example on godbolt.org
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)?

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.

Related

C++: Perfectly forwarded integral_constant can't be evaluated at compile-time with template function [duplicate]

I'm trying to figure out whether GCC or Clang interpret the C++17 standard differently / wrong here.
This is my code, which does compile using GCC 8, but not using Clang 6:
struct BoolHolder {
constexpr static bool b = true;
};
template<bool b>
class Foo {};
int main() {
BoolHolder b;
Foo<b.b> f; // Works
BoolHolder & br = b;
Foo<br.b> f2; // Doesn't work
}
I wonder why that is. Obviously, b.b is a valid constexpr (or the first Foo<b.b> wouldn't be valid). Is br.b not a valid constexpr? Why? The object or the reference itself should have nothing to do with it, since we're accessing a static constexpr member here, right?
If this is really not valid C++17, should the fact that GCC doesn't even warn me (even though I enabled -Wall -Wextra -pedantic) be considered a bug?
Clang is correct. References are evaluated "eagerly" in constant expressions, so to speak. [expr.const]/2.11:
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 id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and
either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
[...]

constexpr constructor gives a different result when evaluated at compile time by GCC

The constructor uses a function taking a reference and returning by value while repeatedly modifying a data member:
constexpr int vv(int x) {return x;}
constexpr int & rr(int & x) {return x;}
constexpr int rv(int & x) {return x;}
constexpr struct S {
int x {0};
template<typename F> constexpr S(F f) {x = f(x) + 1; x = f(x) + 1;}
} s(rv); // s.x is 1 if function rv is used, 2 otherwise.
static_assert(s.x == 2, "");
It is only the function rv which gives the unexpected result when used in the constructor. If vv or rr is passed instead then s.x is 2 as expected.
I noticed the behavior on GCC 5.4.1 ARM and it appears to be the same in all versions of GCC that support C++14. Clang gives the expected result of 2 in all cases. Neither GCC nor Clang give any warnings with Wall and Wextra enabled.
Is this example valid C++14 and a bug in GCC? I read the list of restrictions on constant expressions in the standard and see nothing obvious it violates but I'm not sure I understand all the technical details.
Your code is certainly intended to be well-formed. I believe GCC performs a revised form of memoization; back in C++11, objects could not be modified in constant expressions, hence it was perfectly fine to cache function results. In fact, it's quite apparent from a few Clang bug reports that GCC did exactly that, see e.g. here:
Clang's constexpr implementation does not perform caching. In C++14,
it's not even clear whether caching will be possible, so it seems like
a waste of time to invest in it now.
They obviously had to introduce some additional analysis in C++14, and they made a mistake: they assumed that any constexpr object's subobject cannot be modified during its lifetime. That clearly doesn't hold during the period of construction of the enclosing object. If we use a temporary instead of x, the code compiles. If we put the whole thing into a constexpr function and remove s's constexpr specifier, it works.
Funnily enough, rr's and vv's results are also cached, but the same reference being returned works fine, and call by value avoids that problem altogether :)
Reported as 79520.

Argument only used in unevaluated context within the body of a constexpr function

The following piece of code has successfully compiled with gcc 5.3.0 but has failed to compile with clang 3.7.0. I used the online coliru compilers with the same command line options in both cases: -std=c++14 -O2 -Wall -pedantic -pthread.
#include <cstdio>
// Definition of constexpr function 'foo'.
constexpr std::size_t foo(const int& arg_foo) { return sizeof(arg_foo); }
// Definition of function 'test'.
void test(const int& arg)
{
// The following line produces an error with clang.
constexpr std::size_t res_foo = foo(arg);
// Print the result returned by the 'foo' function.
std::printf("res_foo = %lu\n", res_foo);
}
// Definition of function 'main'.
int main(int argc, const char* argv[])
{
// Test function call.
test(argc);
// Return statement.
return 0;
}
clang rejects it with the following error:
error: constexpr variable 'res_foo' must be initialized by a constant expression
constexpr size_t res_foo = foo(arg);
~~~~^~~~
Because of this difference between the two compilers, I am wondering if this is a valid piece of code. If not, I would like to get a better understanding of why this is the case.
You are mixing const and constexpr values together. The definition of constexpr is a value known at compile time. But the argcvariable is only known at run time (it is a number of arguments passed to your executable). So you cannot assign it to another constexpr variable - res_foo. It is Removing constexpr from the res_foo definition will make your code compilable.
The difference between const and constexpr can be simplified to something like this:
const - I'm not going to change this value
constexpr - This value is known at compile time and I'm not going to change it
My guess is that GCC is able to compile this code with O2 since you are not using the arg_foo argument and the size of it is known at the compile time. But it is still syntactically incorrect - compiler should emit an error, since non-constexpr value is assigned to a constexpr variable.
The program is well-formed, since foo(arg) is a prvalue core constant expression as defined in C++14 5.20/2. In particular, there is no lvalue-to-rvalue conversion during the evaluation, which would make it not a constant expression.

Could non-static member variable be modified in constexpr constructor (C++14)?

struct A {
int a = 0;
constexpr A() { a = 1; }
};
constexpr bool f() {
constexpr A a;
static_assert(a.a == 1, ""); // L1: OK
return a.a == 1;
}
static_assert(f(), ""); // L2: Error, can not modify A::a in constexpr
Online Compiler URL: http://goo.gl/jni6Em
Compiler: clang 3.4 (with -std=c++1y)
System: Linux 3.2
If I delete L2, this code compiles. If I add L2, the compiler complained "modification of object of const-qualified type 'const int' is not allowed in a constant expression". I am not a language lawyer, so I am not sure whether this is true. However, if it is, why compiler didn't complain anything about L1, since it also called A() as constexpr? Is this a bug of clang? Or did I miss anything?
Reference: http://en.cppreference.com/w/cpp/language/constexpr
BTW, if I change "constexpr A a;" to "A a;" (remove constexpr keyword), L1 failed to compile which is expect. However, the compiler didn't complain about L2 anymore.
Online Compiler URL about this: http://goo.gl/AoTzYx
I believe this is just a case of compilers not having caught up to the changes proposed for C++14. Your constexpr constructor satisfies all the conditions listed in ยง7.1.5/4 of N3936. Both gcc and clang fail to compile your code, but for different reasons.
clang complains:
note: modification of object of const-qualified type 'const int' is not allowed in a constant expression
which doesn't make much sense, but reminds me of the C++11 restriction that constexpr member functions are implicitly const (this is a constructor, and that doesn't apply, but the error message is reminiscent of that). This restriction was also lifted for C++14.
gcc's error message is:
error: constexpr constructor does not have empty body
Seems pretty clear that gcc still implements the C++11 rules for constexpr constructors.
Moreover, N3597 lists this example:
struct override_raii {
constexpr override_raii(int &a, int v) : a(a), old(a) {
a = v;
}
constexpr ~override_raii() {
a = old;
}
int &a, old;
};
N3597 was superseded by N3652, which contains the wording found in the current draft. Unfortunately, the earlier example disappears, but, again, nothing in the current wording says you cannot assign values to data members within the body of a constexpr constructor.
Update (2017-10-03)
clang fixed this, but there has been no new release yet: https://bugs.llvm.org/show_bug.cgi?id=19741
(Compiler explorer)

ref qualifier gives error in gcc4.7.2 and vc10

consider the below minimal example.
#include<iostream>
struct A
{
A(){std::cout<<"def"<<'\n';}
void foo()&{std::cout<<"called on lvalue"<<'\n';}
};
int main()
{
A a;
a.foo();
A().foo();
return 0;
}
this gives error about expecting ';' at the end of declaration and and expected un-qualified-id before '{'.
Can i know what i'm doing wrong? in the actual code i want to avoid calling the non-static member function through temporaries.
tried on gcc 4.7.2 and vc2010.
Thanks.
The answer will be short: VC10 and GCC 4.7.2 do not support ref-qualifiers.
However, notice that your foo() function has an lvalue ref qualifier, meaning that you cannot invoke it on temporaries.
If you want this expression to compile as well:
A().foo();
Then you should use const&, or provide an overload for &&, as in this live example.
To work with ref-qualifiers you can use Clang 3.2 or GCC 4.8.1.