I came across a problem with g++ with upcasting of a member pointer in a constexpr context using static_cast. See code example.
When compiling with g++ version 6.3 and 7.0 they give a compilation error saying reinterpret_cast is not a constant expression.
While clang version 4.0 gives no error, which I think is correct since there is no reinterpret_cast here.
Is this a bug in g++ or clang? What is the correct behavior?
struct Base {};
struct Derived : Base
{
int i;
};
struct Ptr
{
constexpr Ptr(int Derived::* p) : p(static_cast<int Base::*>(p)){}
int Base::* p;
};
constexpr Ptr constexpr_ptr(&Derived::i);
Compiler output
g++ -c -std=c++14 test.cpp
test.cpp:17:40: in constexpr expansion of ‘Ptr(&Derived::i)’
test.cpp:11:41: error: a reinterpret_cast is not a constant expression
constexpr Ptr(int Derived::* p) : p(static_cast<int Base::*>(p)){}
^~~~~~~~~~~~~~~~~~~~~~~~~~~
GCC presumably misapprehends [expr.static.cast]/12, which permits your cast and notes that
If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the behavior is undefined.
Since Base is indeed a base of the class containing the member, the behaviour should be defined, and the constructor call a constant expression.
Related
I have a Derived class that inherits from multiple Base classes. Each of these base classes has its own get function. At runtime I have an index corresponding to the base class whose get function I want to call.
I have hundreds of these Base classes so I was thinking of storing their get functions in a constexpr array of pointer-to-member functions, but I ran into a number of inconsistencies between clang, gcc, and msvc and now I'm not sure what the correct behavior is.
I have a different version of the code that fills this array at runtime and it works perfectly, but it's doing needless work.
I reduced the problem to two base classes instead of hundreds and tried three types of pointer-to-member function calls:
runtime downcasting from base to derived
compile time downcasting from base to derived (outside of class definition)
compile time downcasting from base to derived (inside class definition)
I tried three compilers with C+20 support:
Clang works for all three
GCC fails for #3
MSVC fails for #2 and #3
On GCC and MSVC it doesn't set the this pointer to the correct base class. It always uses the address of the first base class, printing 0 instead of 1.
#include <iostream>
struct Base0
{
int value = 0;
void get() { std::cout << value << std::endl; }
};
struct Base1
{
int value = 1;
void get() { std::cout << value << std::endl; }
};
struct Derived : Base0, Base1
{
#ifdef _MSC_VER // static constexpr downcast pointer-to-member fails in msvc
static const inline auto Get0 = reinterpret_cast<void(Derived::*)()>(&Base0::get);
static const inline auto Get1 = reinterpret_cast<void(Derived::*)()>(&Base1::get);
#else
static constexpr auto Get0 = static_cast<void(Derived::*)()>(&Base0::get);
static constexpr auto Get1 = static_cast<void(Derived::*)()>(&Base1::get);
#endif
};
auto runtimeGet0 = static_cast<void(Derived::*)()>(&Base0::get);
auto runtimeGet1 = static_cast<void(Derived::*)()>(&Base1::get);
constexpr auto constexprGet0 = static_cast<void(Derived::*)()>(&Base0::get);
constexpr auto constexprGet1 = static_cast<void(Derived::*)()>(&Base1::get);
int main()
{
Derived d;
// 1
(d.*runtimeGet0)(); // clang: 0, gcc: 0, msvc: 0
(d.*runtimeGet1)(); // clang: 1, gcc: 1, msvc: 1
// 2
(d.*constexprGet0)(); // clang: 0, gcc: 0, msvc: 0
(d.*constexprGet1)(); // clang: 1, gcc: 1, msvc: 0
// 3
(d.*Derived::Get0)(); // clang: 0, gcc: 0, msvc: 0
(d.*Derived::Get1)(); // clang: 1, gcc: 0, msvc: 0
}
Try it here
Questions:
Which compiler is doing the right thing?
Why would storing the pointer-to-member function inside the class definition vs outside make a difference?
Why does MSVC need reinterpret_cast? Note that I'm using the /vmg and /vmm compiler flags. If I comment out the ifdef I get the peculiar error:
error C2440: 'static_cast': cannot convert from 'void (__cdecl
Outer::Base0::* )(void)' to 'void (__cdecl Outer::Derived::* )(void)'
note: Cast from base to derived requires dynamic_cast or static_cast
The implicit member pointer conversion (static_cast is not required) requires that the derived class is complete, which it isn't in your static member initializers, which are not a complete-class context, see [conv.mem]/2.
The result of using reinterpret_cast is unspecified except for the round-trip conversion back to the original type. Using it directly in a member access causes undefined behavior. See [expr.reinterpret.cast]/10.
So, MSVC is correct to reject the static_cast and Clang and GCC should issue a diagnostic for it as well. Given that this should be ill-formed, there is no sense in asking whether the behavior of the program produced by GCC and Clang for #3 is wrong except if the developers intended this to be allowed as an extension in which case I wouldn't know where the intended behavior is documented. constexpr should not change anything about this.
Because using reinterpret_cast like this causes undefined behavior, MSVC is also not wrong in outputting unexpected results for #3, again except if MSVC is intending to support this as an extension, in which case I don't know the intended behavior.
For #2 (d.*constexprGet1)() MSVC is wrong (according to standard-conformance) to output 0. It should be 1. constexpr does not affect the value of the member pointer.
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
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;
[...]
Scott Meyers' Effective C++ recommends that assignment operators should return a reference to *this. GCC's -Weffc++ seems to generalize this principle to recommend that overloads of in-place arithmetic operators (e.g. prefix ++) should also return a reference to an instance of the class type.
But for CRTP, GCC apparently fails to recognize then the correct reference type is being returned:
template <typename DERIVED>
class Foo
{
public:
DERIVED& operator++(void)
{
++data;
return static_cast<DERIVED&>(*this);
}
private:
int data;
};
class Bar : public Foo<Bar>
{ /* ... */ };
Here, DERIVED& is the type that really ought to be returned, because Bar rather than Foo<Bar> is the "real" type of an instance of Bar. But GCC gives the following warning:
operator_return_this.cpp:5:33: warning: prefix ‘DERIVED& Foo<DERIVED>::operator++()’ should return ‘Foo<DERIVED>&’ [-Weffc++]
DERIVED& operator++(void)
^
operator_return_this.cpp: In instantiation of ‘class Foo<Bar>’:
operator_return_this.cpp:15:24: required from here
operator_return_this.cpp:5:18: warning: prefix ‘DERIVED& Foo<DERIVED>::operator++() [with DERIVED = Bar]’ should return ‘Foo<Bar>&’ [-Weffc++]
DERIVED& operator++(void)
The first warning is somewhat sensible--the compiler can't tell that the class is intended to be used for CRTP, so it doesn't realize that DERIVED will inherit from Foo<DERIVED>. (EDIT: Actually, by using a static_cast instead of a C-style cast, you do ensure that DERIVED is really a derived type, so if the compiler postponed issuing this warning until looking at the implementation of the function, it could suppress the false positive even without seeing the definition of Bar.) But the second warning, where it sees the actual CRTP declaration, doesn't make sense to me at all, since it should be able to tell that Bar& counts as Foo<Bar>&.
But when using CRTP with operator overloading, even the first warning is spurious. Does this mean that -Weffc++ simply can't be used with CRTP (assuming you want to avoid spurious warnings)?