constexpr for values passed in by reference - c++

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

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;
[...]

Using a constexpr static member of a reference as template argument

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;
[...]

not-constexpr variable in if constexpr – clang vs. GCC

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.

Constexpr member function in class template

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.

constexpr with untouched non-constexpr arguments: Who is correct, clang or gcc?

I have 4 test cases and I believe that all of them are valid:
constexpr int f(int const& /*unused*/){
return 1;
}
void g(int const& p){
constexpr int a = f(p); // clang error, gcc valid
int v = 0;
constexpr int b = f(v); // clang valid, gcc valid
int const& r = v;
constexpr int c = f(r); // clang error, gcc error
int n = p;
constexpr int d = f(n); // clang valid, gcc valid
}
int main(){
int p = 0;
g(p);
}
Clang and GCC differ only in the first test case.
I tested with clang 4 & 5 (20170319) and with GCC 7.0.1 (20170221).
If I'm right it would massively simplify the usage of boost::hana in static_assert's.
[expr.const]/2:
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;
[...]
Neither condition is satisfied for p or r. Therefore neither f(p) nor f(r) is a core constant expression and hence neither can be used to initialize a constexpr variable. Clang is correct.