GCC 8.2.1 and MSVC 19.20 compile the below code but Clang 8.0.0 and ICC 19.0.1 fail to do so.
// Base class.
struct Base {};
// Data class.
struct Data { int foo; };
// Derived class.
struct Derived : Base, Data { int bar; };
// Main function.
int main()
{
constexpr int Data::* data_p{ &Data::foo };
constexpr int Derived::* derived_p{ data_p };
constexpr int Base::* base_p{ static_cast<int Base::*>(derived_p) };
return (base_p == nullptr);
}
The error message with Clang 8.0.0 is the following:
case.cpp:16:33: error: constexpr variable 'base_p' must be initialized by a constant expression
constexpr int Base::* base_p{ static_cast<int Base::*>(derived_p) };
~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I noticed that it compiles fine with Clang in two cases:
constexpr int Derived::* derived_p{ data_p };
with constexpr int Derived::* derived_p{ &Derived::bar };
.Should the constexpr expression (the one that makes Clang and ICC fail) compile?
I believe GCC and MSVC are correct, this code should compile.
data_p
points to the member foo
of Data
. derived_p
points to the member foo
of the Data
base class subobject of a Derived
via implicit pointer to member conversion [conv.mem]/2.
From [expr.static.cast]/12
A prvalue of type “pointer to member of
D
of type cv1T
” can be converted to a prvalue of type “pointer to member ofB
of type cv2T
”, whereB
is a base class ofD
, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. […] If classB
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. [ Note: Although classB
need not contain the original member, the dynamic type of the object with which indirection through the pointer to member is performed must contain the original member; see [expr.mptr.oper]. — end note ]
As pointed out by @geza in his comment below, the class Base
is a base class of Derived
, the latter of which contains the original member Data::foo
in its Data
base class subobject (the Note in the quote above would seem to be further evidence in support of this interpretation). Thus, the static_cast
used to initialize base_p
is well-formed and has well-defined behavior. The resulting pointer points to the Data::foo
member of a Derived
object from the perspective of the Base
base class subobject of that Derived
object.
To initialize a constexpr
object, a constant expression is required [dcl.constexpr]/9. Our expression (the result of the static_cast
) is a core constant expression because there is nothing in [expr.const]/2 that would say otherwise. And it is also a constant expression because it is a prvalue that satisfies all the constraints laid out in [expr.const]/5.
I don't think this last line is legal at all, constexpr
or not.
You can convert a pointer to member of a base class to a pointer to member of a derived class, but you can't do the inverse. In relation to conversion between pointers to class instances themselves, pointer-to-member conversions are contravariant. This is why you need the static_cast
to force the compiler to accept this input, even if Base
has an int
data member which you could refer to with a pointer to member (see 2. below).
This makes sense, too: a Derived
is-a Base
, hence a Derived
instance has a sub-object of its parent Base
class. Now, a pointer to member isn't really a pointer, it's an offset, only usable with an address to an actual instance. Any offset within Base
is also a valid offset within Derived
, but some offsets within Derived
aren't valid offsets within Base
.
Base
has no int
data member. How would you want to use this pointer to member anyway? The offset it captures might refer to the Data
suboject within a Derived
instance, but this should be UB at runtime, and a compiler error at compile time.
So, gcc
should also reject the snippet, clang
and icc
are correct about it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With