Я считаю, что GCC и MSVC верны, этот код должен компилироваться.
data_p
указывает на член foo
из Data
. derived_p
указывает на член foo
подобъекта базового класса Data
в Derived
посредством неявного указателя на преобразование члена [conv.mem] / 2 .
С [expr.static.cast] / 12
Значение типа «указатель на член D
типа cv1 T
» может быть преобразовано в значение типа «указатель на член B
типа cv2 T
”, где B
- базовый класс D
, если cv2 - это та же квалификация cv, что и, или более высокая квалификация cv, чем cv1 . […] Если класс B
содержит исходный член или является базовым или производным классом класса, содержащего исходный член, результирующий указатель на член указывает на исходный член. В противном случае поведение не определено. [Примечание: хотя класс B
не обязательно должен содержать исходный член, динамический тип объекта, с которым выполняется перенаправление через указатель на член, должен содержать исходный член; см. [expr.mptr.oper]. - конец примечания]
Как указал @geza в своем комментарии ниже, класс Base
является базовым классом Derived
, последний из которых содержит исходный член Data::foo
в своем подобъекте Data
базового класса (Примечание в приведенной выше цитате, казалось бы, дополнительные доказательства в поддержку этой интерпретации). Таким образом, static_cast
, используемый для инициализации base_p
, является правильно сформированным и имеет четко определенное поведение. Полученный указатель указывает на член Data::foo
объекта Derived
с точки зрения подобъекта базового класса Base
этого объекта Derived
.
Для инициализации объекта constexpr
требуется постоянное выражение [dcl.constexpr] / 9 . Наше выражение (результат static_cast
) является основным константным выражением, поскольку в [expr.const] / 2 нет ничего, что могло бы сказать иначе. И это также константное выражение, потому что это prvalue, которое удовлетворяет всем ограничениям, изложенным в [expr.const] / 5 .