Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constexpr pointer to data member conversion

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:

  • remove the constexpr from the last definition
  • replace the line 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?

like image 867
Ankur deDev Avatar asked Apr 25 '19 07:04

Ankur deDev


2 Answers

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 cv1 T” can be converted to a prvalue of type “pointer to member of B of type cv2 T”, where B is a base class of D, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. […] 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. [ Note: Although class B 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_pis 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.

like image 109
Michael Kenzel Avatar answered Sep 28 '22 17:09

Michael Kenzel


I don't think this last line is legal at all, constexpr or not.

  1. 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.

  2. 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.

like image 37
lubgr Avatar answered Sep 28 '22 17:09

lubgr