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;
[...]
Related
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;
[...]
I had this piece of code which compiles
#include <bitset>
struct A{
std::bitset<50> b; };
void test(A a){
static_assert(sizeof(int)*8 < a.b.size(), "can't accomodate int in bitset");
int x = 5;
a.b = x; }
int main(){
A a;
test(a); }
But this doesn't
#include <bitset>
struct A{
std::bitset<50> b;
};
void test(A& a){
static_assert(sizeof(int)*8 < a.b.size(), "can't accomodate int in bitset");
int x = 5;
a.b = x;
}
int main(){
A a;
test(a);
}
Fails with this error
const.cpp: In function ‘void test(A&)’: const.cpp:8:5: error: non-constant condition for static assertion
static_assert(sizeof(int)*8 < a.b.size(), "can't accomodate int in bitset");
const.cpp:8:5: error: ‘a’ is not a constant expression
Why is a.b.size() not treated as a constexpr in the second case? Isn't the constexpr for std::bitset::size() the one that should be treated as const as per the reference? Or does the non-const reference passed in the second case trigger the compiler to generate the error?
Compiler version:
g++ 4.8.4 on Ubuntu 14.0.4, compiled with g++ const.cpp -std=c++1y
A& a is not usable in constant expressions, making your program ill-formed.
The rule forbidding a.b.size() to be a constant expression with a being a A& is the following:
[expr.const]/3
A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is of reference type or of const-qualified integral or enumeration type, and its initializer is a constant initializer.
In your case, the variable a is:
not declared constexpr (as a function argument, it wouldn't make sense),
and is not an integral or enumeration type,
and is not a reference whose initialization is a constant initializer:
[expr.const]/2
A constant initializer for a variable or temporary object o is an initializer for which interpreting its full-expression as a constant-expression results in a constant expression, except that if o is an object, such an initializer may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.
Take the following reduced example:
struct s { constexpr static bool true_value() { return true; } };
void assert_on(s const& ref)
{
static_assert(ref.true_value());
}
int main()
{
assert_on(s{});
}
gcc-9 wrongly accepts it1, but clang-8 produce the right diagnostic:
error: static_assert expression is not an integral constant expression
Full demo: https://godbolt.org/z/t_-Ubj
1) This is GCC bug #66477, active from version 5.1 and yet to be resolved.
With pointers from others1, it appears to be a compiler bug in both gcc and clang. It's fixed in gcc 5.1 but is, as of 2019-07-25, still a bug in clang 8.0
This is a bug of gcc, introduced in version 5.1. See other answer.
1)
Seems to be a (now fixed) compiler bug as it complies with the latest gcc version: https://godbolt.org/z/ZaDXED
struct A{
constexpr operator bool()const{ return true; }
};
int main(){
auto f = [](auto v){ if constexpr(v){} };
A a;
f(a);
}
clang 6 accepts the Code, GCC 8 rejects it with:
$ g++ -std=c++17 main.cpp
main.cpp: In lambda function:
main.cpp:6:37: error: 'v' is not a constant expression
auto f = [](auto v){ if constexpr(v){} };
^
Who is correct and why?
When I take the parameter per reference, both reject the code:
struct A{
constexpr operator bool()const{ return true; }
};
int main(){
auto f = [](auto& v){ if constexpr(v){} };
constexpr A a;
f(a);
}
Compiled with clang 6:
$ clang++ -std=c++17 main.cpp
main.cpp:6:40: error: constexpr if condition is not a constant expression
auto f = [](auto& v){ if constexpr(v){} };
^
main.cpp:8:6: note: in instantiation of function template specialization
'main()::(anonymous class)::operator()<const A>' requested here
f(a);
^
1 error generated.
When I copy the parameter into a local variable both accept the code:
struct A{
constexpr operator bool()const{ return true; }
};
int main(){
auto f = [](auto v){ auto x = v; if constexpr(x){} };
A a;
f(a);
}
Edit: I am sure that the second and third cases will be handled correctly by both compilers. I don't know what the rule is, though.
In the first case I suspect that clang is right, because the case resembles the second. I would like to know if in the first case clang or GCC is correct and which rules in the second case makes the use of the not-constexpr variable v invalid and in the third case x valid.
Edit 2: First Question is clear now:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84421
clang was right, GCC 7 accepted the code as well. The bug will be fixed in the final version of GCC 8.
Clang is correct in all cases. [Full disclosure: I'm a Clang developer]
The question in all cases reduces to this: can we call a constexpr member function on v within a constant expression?
To answer this question, we need to look at [expr.const]p2, which says:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.8.1), 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;
...
None of the other rules prohibit any of your examples. In particular, you are allowed to name local variables in a constant expression if they are not of reference type. (You are not allowed to perform lvalue-to-rvalue conversions on them -- that is, read their values -- unless their value is known (for instance, because they're constexpr), and you're not allowed to end up referring to the address of such a variable, but you are allowed to name them.)
The reason that the rules are different for entities of reference type is that merely naming an entity of reference type causes the reference to be immediately resolved, even if you don't do anything with the result, and resolving a reference requires knowing what it's bound to.
So: the first example is valid. The *this value of the constexpr member function is bound to the local variable a. It doesn't matter that we don't know what object that is, because the evaluation doesn't care.
The second example (where v is of reference type) is ill-formed. Merely naming v requires resolving it to the object it's bound to, which can't be done as part of the constant expression evaluation because we have no idea what it'll end up being bound to. It doesn't matter that the later evaluation steps won't use the resulting object; references are resolved immediately when they're named.
The third example is valid for the same reason as the first. Notably, the third example remains valid even if you change v to be of reference type:
auto f = [](auto &v) { auto x = v; if constexpr (x) {} };
A a;
f(a);
... because x is, once again, a local variable that we can name within a constant expression.
The following code fails to compile:
// template<class>
struct S {
int g() const {
return 0;
}
constexpr int f() const {
return g();
}
};
int main()
{
S /*<int>*/ s;
auto z = s.f();
}
GCC, for example, complains: error: call to non-constexpr function ‘int S::g() const’. This is perfectly reasonable. But if I turn S into a template, the code compiles (checked with MSVC 15.3, GCC 7.1.0, clang 4.0.1).
Why? Does constexpr has any special meaning in class templates?
As far as I understand it, this code is incorrect, but the standard does not require that compilers produce an error (why?).
Per [dcl.constexpr]
The definition of a constexpr function shall satisfy the following constraints:
...every constructor call and implicit conversion used in initializing the return value (6.6.3, 8.5) shall be
one of those allowed in a constant expression
A call to g() is not allowed in a constant expression. Per [expr.const]:
A conditional-expression is a core constant expression unless it involves one of the following as a potentially
evaluated subexpression...:
— an invocation of a function other than [...] a constexpr function
It looks like some compilers may allow you to do what you're doing because z isn't declared constexpr so the value doesn't need to be known at compile-time. If you change your code to
constexpr auto z = s.f();
you'll note that all those compilers will proceed to barf, template or not.
I've run into what seems a counterintuitive error, namely, the inability to assign the value of a constexpr function to a constexpr literal (hope I'm using the language right). Here's the example:
class MyClass {
public:
static constexpr int FooValue(int n) { return n + 5; }
static constexpr int Foo5 = FooValue(5); // compiler error
static constexpr int Foo5Alt(void) { return FooValue(5); } // OK
};
In GCC 4.8.4, Foo5 is flagged for field initializer is not constant. Found this thread suggesting that the older version of GCC might be the culprit. So I plugged it into Coliru (GCC 6.2.0) and got the error 'static constexpr int MyClass::FooValue(int)' called in a constant expression before its definition is complete. I added Foo5Alt() which returns its value as a constexpr function rather than literal, and that compiles fine.
I guess I'm not following why FooValue(5) can't be used as the initializer for Foo5. The definition for FooValue(int n) is complete, isn't it? { return n + 5; } is the entire definition. constexpr denotes an expression that can be fully evaluated at compile time, so why can it not be used to define the return value of a constexpr literal?
What subtlety of C++ am I missing?
In C++, inline definitions of member functions for a class are only parsed after the declaration of the class is complete.
So even though the compiler "knows" about MyClass::FooValue(int), it hasn't "seen" its definition yet, and hence it can't be used in a constexpr expression.
A general workaround for this is to stick to constexpr member functions, or declare constexpr constants outside the class.
According to the standard, MyClass is considered an incomplete type when you try to invoke FooValue to initialize Foo5. Therefore, you cannot use its members as you did.
The type is considered a completely-defined object type (or complete type) at the closing }.
On the other side, the class is regarded as complete within function bodies. That's why Foo5Alt compiles just fine.
See [class.mem]/6 for further details.