Reputation: 505
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?
Upvotes: 7
Views: 1216
Reputation: 15941
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.
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.
Upvotes: 3
Reputation: 38277
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.
Upvotes: 1