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

Сообщение об ошибке с 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)?


person Ankur deDev    schedule 25.04.2019    source источник
comment
Спасибо за ответы, я заполнил отчет об ошибке здесь   -  person Ankur deDev    schedule 24.06.2019


Ответы (2)


Я считаю, что GCC и MSVC верны, этот код должен компилироваться.

data_p указывает на члена foo из Data. derived_p указывает на член foo подобъекта базового класса Data объекта Derived через неявный указатель на преобразование члена [conv.mem] / 2.

Из [expr.static.cast] / 12

Prvalue типа «указатель на член D типа cv1 T» может быть преобразован в prvalue типа «указатель на член B типа cv2 T», где B является базовым классом D, если cv2 - это то же cv-квалификация, что и cv1, или более высокая квалификация, чем у 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.

person Michael Kenzel    schedule 25.04.2019
comment
Как насчет этой интерпретации? Base - это базовый класс Derived. Derived содержит foo (по Data). Означает ли сдерживание немедленное сдерживание? - person geza; 25.04.2019
comment
@YSC Но это не ошибка компилятора, чтобы не отклонить его. dcl.constexpr / 9 сообщает, что инициализатор будет постоянным выражением. Как это не ошибка, если не диагностировать случай, когда инициализатор не является константным выражением? - person Language Lawyer; 25.04.2019
comment
@LanguageLawyer вам не хватает контекста. Мой комментарий устарел. - person YSC; 25.04.2019
comment
@MichaelKenzel Я согласен с вами и удалил свой ответ, нет смысла его исправлять, чтобы двое говорили одно и то же. - person YSC; 25.04.2019
comment
@MichaelKenzel Примечание. Я думаю, что это предложение должно появляются в вашем ответе: это то, что связывает [expr.static.cast]/12 с [conv.mem]/2 - person YSC; 25.04.2019
comment
@YSC: по иронии судьбы, я уже не так уверен в этом. Я думаю, что формулировка в стандарте здесь крайне расплывчата. На самом деле я подумывал открыть для этого новый вопрос. Имхо не ясно, что именно означает, что класс содержит член. По крайней мере, даже после обширных поисков я не смог найти четкого определения этого в стандарте. Был бы очень рад узнать, сможет ли кто-нибудь найти что-то, подтверждающее или опровергающее нашу интерпретацию, изложенную выше ... - person Michael Kenzel; 25.04.2019
comment
@MichaelKenzel убедил меня Результат относится к участнику в случае D из B. - person YSC; 25.04.2019
comment
@YSC Мой комментарий неверен вне контекста? Реализациям разрешено не выдавать диагностическое сообщение, когда используется неконстантное выражение, когда требуется константа? - person Language Lawyer; 27.04.2019

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

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

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

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

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

person lubgr    schedule 25.04.2019
comment
@YSC Кажется, нам всем потребовалось несколько итераций, чтобы получить правильные ответы :) - person lubgr; 25.04.2019
comment
Убеждены ли аргументы Гезы, Майкла Кензеля и YSC? - person Ankur deDev; 26.04.2019