Constexpr指向数据成员转换的指​​针

Ank*_*Dev 6 c++ pointer-to-member language-lawyer constexpr

GCC 8.2.1和MSVC 19.20编译以下代码,但是Clang 8.0.0和ICC 19.0.1不能这样做。

// 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);
}
Run Code Online (Sandbox Code Playgroud)

Clang 8.0.0的错误消息如下:

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) };
                              ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

我注意到它在两种情况下都可以使用Clang进行编译:

  • 从最后一个定义中删除constexpr
  • 更换线constexpr int Derived::* derived_p{ data_p };constexpr int Derived::* derived_p{ &Derived::bar };

应该编译constexpr表达式(使Clang和ICC失败的表达式)吗?

Mic*_*zel 3

我相信 GCC 和 MSVC 是正确的,这段代码应该可以编译。

\n\n

data_p指向foo的成员Data。通过指向成员转换的隐式指针derived_p指向[conv.mem]/2的基类子对象foo的成员。DataDerived

\n\n

来自[expr.static.cast]/12

\n\n
\n

指向cv1D \xe2\x80\x9d类型的成员的\xe2\x80\x9c 类型的指针的纯右值可以转换为指向cv2 \xe2\x80\x9d类型的成员的 \xe2\x80\x9c 类型的指针的纯右值,其中是 的基类,如果cv2与cv1 的cv 资格相同或更高。[\xe2\x80\xa6]如果类包含原始成员,或者是包含原始成员的类的基类或派生类,则生成的指向成员的指针将指向原始成员。否则,行为是未定义的。[\xe2\x80\x89注:虽然类 TB TBDBB不需要包含原始成员,但是通过指向成员的指针执行间接操作的对象的动态类型必须包含原始成员;参见[expr.mptr.oper]。\xe2\x80\x89\xe2\x80\x94\xe2\x80\x89尾注\xe2\x80\x89]

\n
\n\n

正如 @geza 在下面的评论中指出的,该类Base是 的基类Derived,后者包含Data::fooData基类子对象中的原始成员(上面引用中的注释似乎是支持这种解释的进一步证据)。因此,static_cast用于初始化的函数格式base_p良好,并且具有明确定义的行为。从该对象的基类子对象的角度来看,生成的指针指向Data::foo该对象的成员。DerivedBaseDerived

\n\n

要初始化constexpr对象,需要常量表达式[dcl.constexpr]/9。我们的表达式( 的结果)是一个核心常量表达式,因为[expr.const]/2static_cast中没有任何其他内容。它也是一个常量表达式,因为它是一个满足[expr.const]/5中规定的所有约束的纯右值。

\n

  • 这个解读又如何呢?“Base”是“Derived”的基类。`Derived` 包含 `foo` (通过 `Data`)。遏制是否意味着“立即”遏制? (2认同)