Constexpr указатель на преобразование элемента данных - PullRequest
6 голосов
/ 25 апреля 2019

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);
}

Сообщение об ошибке с 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) };
                              ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Я заметил, что он прекрасно компилируется с Clang в двух случаях:

  • удалить constexpr из последнего определения
  • заменить строку constexpr int Derived::* derived_p{ data_p }; наconstexpr int Derived::* derived_p{ &Derived::bar };.

Должно ли скомпилироваться выражение constexpr (которое приводит к сбою Clang и ICC)?

Ответы [ 2 ]

3 голосов
/ 25 апреля 2019

Я считаю, что 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 .

1 голос
/ 25 апреля 2019

Я не думаю, что эта последняя строка является допустимой, constexpr или нет.

  1. Вы можете преобразовать указатель на член базового класса в указатель на членпроизводного класса, но вы не можете сделать обратное.Что касается преобразования между указателями в сами экземпляры классов, преобразования указатель-на-член противоречивы .Вот почему вам нужно static_cast, чтобы заставить компилятор принять этот ввод, даже если Base имеет элемент данных int, на который вы можете ссылаться указателем на член (см. 2. ниже).

    Это также имеет смысл: Derived is-a Base, следовательно, экземпляр Derived имеет подобъект своего родительского класса Base.Теперь указатель на член на самом деле не указатель, это смещение , который можно использовать только с адресом фактического экземпляра.Любое смещение в Base также является допустимым смещением в Derived, но некоторые смещения в Derived не являются действительными смещениями в Base.

  2. Base не имеетint элемент данных.Как бы вы хотели использовать этот указатель для члена в любом случае?Полученное смещение может относиться к подобъекту Data в экземпляре Derived, но это должно быть UB во время выполнения и ошибка компилятора во время компиляции.

Итак, gcc также должен отклонять фрагмент, clang и icc верны в этом.

...