массив constexpr объектов constexpr с использованием move ctor

У меня есть класс с конструктором значений constexpr, но нет копирования или перемещения ctor

class  C {
    public:
        constexpr C(int) { }
        C(const C&) = delete;
        C& operator=(const C&) = delete;
};

int main() {
    constexpr C arr[] = {1, 2};
}

Я обнаружил, что этот код не работает, потому что на самом деле он пытается использовать конструктор перемещения для C, а не конструктор значений для построения на месте. Одна проблема заключается в том, что я хочу, чтобы этот объект был неподвижным (для целей тестирования), но я подумал: «Хорошо, хорошо, я добавлю конструктор перемещения».

class  C {
    public:
        constexpr C(int) { }
        C(const C&) = delete;
        C& operator=(const C&) = delete;
        C& operator=(C&&) = delete;

        C(C&&) { /*something*/ } // added, assume this must be non trivial
};

Хорошо, хорошо, теперь он использует конструктор перемещения, и все работает под gcc, но когда я использую clang, он жалуется, потому что конструктор перемещения не помечен constexpr

error: constexpr variable 'arr' must be initialized by a constant expression
    constexpr C arr[] = {1, 2};

Если я отмечу конструктор перемещения constexpr, он будет работать под gcc и clang, но проблема в том, что я хочу иметь код в конструкторе перемещения, если он вообще запускается, а конструкторы constexpr должны иметь пустые тела. (Причина, по которой у меня есть код в движении, не стоит вдаваться в подробности).

Так кто здесь прав? Я склоняюсь к тому, что clang будет правильным для отклонения кода.

ПРИМЕЧАНИЕ

Он компилируется со списками инициализаторов и не копируемыми неподвижными объектами, как показано ниже:

class  C {
    public:
        constexpr C(int) { }
        C(const C&) = delete;
        C& operator=(const C&) = delete;
        C& operator=(C&&) = delete;
        C(C&&) = delete;

};

int main() {
    constexpr C arr[] = {{1}, {2}};
}

Меня больше всего беспокоит, какой компилятор выше правильный.


person Ryan Haining    schedule 04.12.2014    source источник
comment
GCC не работает с включенным -fno-elide-constructors, поэтому я предполагаю, что он также должен дать сбой с включенным копированием, как это делает Clang. Используя {{1},{2}}, вы копируете-список-инициализируете объекты, которые не вызывают какой-либо копирующий-ctor, вызывая constexpr ctor напрямую, поэтому ошибки не ожидается, и оба компилятора верны   -  person Piotr Skotnicki    schedule 04.12.2014


Ответы (2)


Так кто здесь прав?

Clang прав в отклонении кода. [expr.const]/2:

условное-выражение e является основным константным выражением, если только вычисление e в соответствии с правилами абстрактной машины (1.9) не будет оценивать одно из следующих выражений:

  • вызов функции, отличной от конструктора constexpr для литерального класса, функции constexpr или неявный вызов тривиального деструктора (12.4)

Очевидно, что ваш конструктор перемещения не является конструктором constexpr - [dcl.constexpr]/2

Точно так же спецификатор constexpr, используемый в объявлении конструктора, объявляет этот конструктор конструктором constexpr.

И требования к инициализатору объекта constexpr находятся в [dcl.constexpr]/9:

[…] каждое полное выражение, которое появляется в его инициализаторе, должно быть постоянным выражением. [ Примечание: Каждое неявное преобразование, используемое при преобразовании выражений инициализатора, и каждый вызов конструктора, используемый для инициализации, является частью такого полного выражения. — конец примечания ]

Наконец, конструктор перемещения вызывается копированием-инициализацией элементов массива с соответствующими предложениями инициализатора — [dcl.init]:

В противном случае (т. е. для оставшихся случаев инициализации копирования) определяемые пользователем последовательности преобразования, которые могут преобразовывать исходный тип в целевой тип или (при использовании функции преобразования) в его производный класс, перечисляются, как описано в 13.3. 1.4, а лучший выбирается через разрешение перегрузки (13.3). Если преобразование не может быть выполнено или неоднозначно, инициализация имеет неправильный формат. Выбранная функция вызывается с выражением инициализатора в качестве аргумента; Если функция является конструктором, вызов инициализирует временную версию без уточнения cv целевого типа. Временное является ценностью. Результат вызова (который является временным для случая конструктора) затем используется для прямой инициализации в соответствии с приведенными выше правилами объекта, который является местом назначения инициализации копирования.

Во втором примере применяется copy-list-initializationвременное не вводится.

Кстати: GCC 4.9 не компилирует вышеуказанное , даже без каких-либо предупреждающих флагов.

person Columbo    schedule 04.12.2014
comment
используя g++ 4.9.2 и c++11, когда я получил это - person Ryan Haining; 05.12.2014

§8.5 [dcl.init]/p17:

Семантика инициализаторов следующая. Целевой тип — это тип инициализируемого объекта или ссылки, а исходный тип — это тип выражения инициализатора. Если инициализатор не является одиночным (возможно, заключенным в скобки) выражением, исходный тип не определен.

  • Если инициализатором является (не заключенный в скобки) список инициализации в фигурных скобках, объект или ссылка инициализируется списком (8.5.4).
  • [...]
  • If the destination type is a (possibly cv-qualified) class type:
    • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, [...]
    • В противном случае (т. е. для оставшихся случаев инициализации копирования) определяемые пользователем последовательности преобразования, которые могут преобразовывать исходный тип в целевой тип или (при использовании функции преобразования) в его производный класс, перечисляются, как описано в 13.3. 1.4, а лучший выбирается через разрешение перегрузки (13.3). Если преобразование не может быть выполнено или неоднозначно, инициализация имеет неправильный формат. Выбранная функция вызывается с выражением инициализатора в качестве аргумента; если функция является конструктором, вызов инициализирует временную версию cv-unqualified целевого типа. Временное является ценностью. Результат вызова (который является временным для случая конструктора) затем используется для прямой инициализации в соответствии с приведенными выше правилами объекта, который является местом назначения инициализации копирования. В некоторых случаях реализации разрешается устранять копирование, присущее этой прямой инициализации, путем создания промежуточного результата непосредственно в инициализируемом объекте; см. 12.2, 12.8.
  • [...]

§8.5.1 [dcl.init.aggr]/p2:

Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся в качестве инициализаторов для членов агрегата в порядке возрастания индекса или членов. Каждый элемент инициализируется копированием из соответствующего предложения-инициализатора. Если предложение-инициализатор является выражением и для его преобразования требуется сужающее преобразование (8.5.4), программа имеет неправильный формат. [ Примечание: Если предложение-инициализатор само по себе является списком инициализаторов, элемент инициализируется списком, что приведет к рекурсивному применению правил в этом разделе, если член представляет собой совокупность. —конец примечания ]

§8.5.4 [dcl.init.list]/p3:

Список-инициализация объекта или ссылки типа T определяется следующим образом:

  • Если T является агрегатом, выполняется агрегатная инициализация (8.5.1).
  • [...]
  • В противном случае, если T является типом класса, рассматриваются конструкторы. Перечисляются применимые конструкторы, и лучший из них выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7). Если для преобразования какого-либо из аргументов требуется сужающее преобразование (см. ниже), программа некорректна.
  • [...]

Для constexpr C arr[] = {1, 2}; агрегатная инициализация копирует-инициализирует каждый элемент из соответствующего предложения-инициализатора, т. е. 1 и 2. Как описано в §8.5 [dcl.init]/p17, создается временный C, а затем выполняется прямая инициализация элемента массива из временного, для чего требуется доступный конструктор копирования или перемещения. (Копирование/перемещение можно опустить, но конструктор должен оставаться доступным.)

Для constexpr C arr[] = {{1}, {2}}; вместо этого элементы copy-list-initialized, что не создает временных объектов (обратите внимание на отсутствие каких-либо упоминаний о создании временных объектов в §8.5.4 [dcl.init.list]/ п3).

person T.C.    schedule 04.12.2014