Какие изменения в С++ сделали работу по инициализации копии для класса с явным конструктором?

Рассмотрим этот код:

struct X{
    explicit X(){}
    explicit X(const X&){}
};

void foo(X a = X()){}

int main(){}

Используя стандарт С++ 14, GCC 7.1 и clang 4.0 отклоняют код, который я ожидал.

Однако, используя С++ 17 (-std=c++1z), они оба принимают код. Какое правило изменилось?


Для обоих компиляторов, которые демонстрируют такое же поведение, я сомневаюсь, что это ошибка. Но, насколько я могу судить, в последнем проекте все еще говорится, что аргумент по умолчанию использует семантику copy-initialization 1. Опять же, мы знаем, что конструкторы explicit будут допускать только прямую инициализацию 2.

1: dcl.fct.default/5; 2: class.conv.ctor/2

Ответы

Ответ 1

Поскольку поведение copy elision изменяется с С++ 17; для этого случая оптимизация обязательна.

При следующих обстоятельствах компиляторы должны опускать копировать и перемещать конструкторы объектов класса, даже если копировать/перемещать конструктор и деструктор имеют наблюдаемые побочные эффекты:

  • При инициализации, если выражение инициализатора является prvalue и cv-неквалифицированная версия типа источника - это тот же класс, что и класс адресата, выражение инициализатора используется для инициализировать объект назначения:

    T x = T(T(T())); // only one call to default constructor of T, to initialize x
    

и инициализация копирования:

Эффекты инициализации копии:

  • Во-первых, если T - тип класса, а инициализатор - это prvalue выражение, чей неквалифицированный тип является тем же классом, что и T, самого инициализатора, скорее, что временное материализованное из него используется для инициализации целевого объекта: см. copy elision (начиная с С++ 17)

  • Если T - тип класса, а cv-неквалифицированная версия типа other T или класс, полученный из T, неявные конструкторы T, и наилучшее совпадение выбирается с помощью разрешения перегрузки. Затем конструктор вызывается для инициализации объекта.

Это означает, что для X a = X(), значение a будет построено по умолчанию напрямую, конструкторы copy/move и их побочные эффекты будут полностью исключены. Выбор неявных конструкторов для разрешения перегрузки не будет иметь места, что требуется в С++ 14 (и раньше). Для этих гарантированных случаев конструкторы copy/move не участвуют, тогда не имеет значения, являются ли они explicit или нет.