Назначение явных конструкторов по умолчанию

Недавно я заметил класс в C++0x, который вызывает явный конструктор по умолчанию. Однако я не могу придумать сценарий, в котором конструктор по умолчанию может быть вызван неявно. Похоже, довольно бессмысленный спецификатор. Я думал, может быть, это будет запрещено Class c; в пользу Class c = Class(); но, похоже, это не так.

Некоторые соответствующие цитаты из C++0x FCD, поскольку мне легче ориентироваться [подобный текст существует в C++03, если не в тех же местах]

12.3.1.3 [class.conv.ctor]

Конструктор по умолчанию может быть явным конструктором; такой конструктор будет использоваться для выполнения инициализации по умолчанию или инициализации значения (8.5).

Далее приводится пример явного конструктора по умолчанию, но он просто имитирует приведенный выше пример.

8.5.6 [decl.init]

По умолчанию инициализировать объект типа T означает:

- если T является (возможно, cv-квалифицированным) типом класса (раздел 9), вызывается конструктор по умолчанию для T (и инициализация некорректна, если у T нет доступного конструктора по умолчанию);

8.5.7 [decl.init]

Инициализировать значение объекта типа T означает:

- если T является (возможно, cv-квалифицированным) типом класса (раздел 9) с предоставленным пользователем конструктором (12.1), то вызывается конструктор по умолчанию для T (и инициализация является некорректной, если у T нет доступного конструктора по умолчанию));

В обоих случаях стандарт требует вызова конструктора по умолчанию. Но это то, что произошло бы, если бы конструктор по умолчанию не был явным. Ради полноты:

8.5.11 [decl.init]

Если для объекта не указан инициализатор, объект инициализируется по умолчанию;

Из того, что я могу сказать, это просто оставляет преобразование без данных. Что не имеет смысла. Лучшее, что я могу придумать, будет следующим:

void function(Class c);
int main() {
  function(); //implicitly convert from no parameter to a single parameter
}

Но очевидно, что C++ не обрабатывает аргументы по умолчанию. Что еще может сделать explicit Class(); вести себя иначе, чем Class();?

Конкретный пример, который породил этот вопрос, был std::function [20.8.14.2 func.wrap.func]. Требуется несколько конвертирующих конструкторов, ни один из которых не помечен явно, но конструктор по умолчанию -.

1 ответ

Решение

Это объявляет явный конструктор по умолчанию:

struct A {
  explicit A(int a1 = 0);
};

A a = 0; /* not allowed */
A b; /* allowed */
A c(0); /* allowed */

В случае отсутствия параметра, как в следующем примере, explicit избыточно

struct A {
  /* explicit is redundant. */
  explicit A();
};

В некоторых черновиках C++0x (я полагаю, это был n3035), это имело значение следующим образом:

A a = {}; /* error! */
A b{}; /* alright */

void function(A a);
void f() { function({}); /* error! */ }

Но в FCD они изменили это (хотя, я подозреваю, что они не имели в виду эту особую причину) в том, что все три случая инициализируют значение соответствующим объектом. Инициализация значения не выполняет танец с перегрузочным разрешением и, следовательно, не сработает на явных конструкторах.

Если явно не указано иное, все стандартные ссылки ниже относятся к N4659: рабочий черновик, выпущенный после Kona за март 2017 г. / DIS C++17.


(Этот ответ посвящен явным конструкторам по умолчанию, которые не имеют параметров)


Случай № 1 [отC++11 до C++20]: пусто {} copy-list-initialization для неагрегатов запрещает использование явных конструкторов по умолчанию

В соответствии с [over.match.list] / 1 [выделено мной ]:

Когда объекты неагрегированного типа класса T инициализируются списком, так что [dcl.init.list] указывает, что разрешение перегрузки выполняется в соответствии с правилами в этом разделе, разрешение перегрузки выбирает конструктор в два этапа:

  • (1.1) Первоначально функции-кандидаты являются конструкторами списка инициализаторов ([dcl.init.list]) класса. T а список аргументов состоит из списка инициализаторов как одного аргумента.
  • (1.2) Если жизнеспособный конструктор списка инициализаторов не найден, разрешение перегрузки выполняется снова, где все функции-кандидаты являются конструкторами класса. T а список аргументов состоит из элементов списка инициализатора.

Если в списке инициализаторов нет элементов и Tимеет конструктор по умолчанию, первая фаза опускается. При инициализации списка копирования, если explicitконструктор выбран, инициализация выполнена некорректно.[  Примечание: это отличается от других ситуаций ([over.match.ctor], [over.match.copy]), где только конструкторы преобразования рассматриваются для инициализации копирования. Это ограничение применяется только в том случае, если эта инициализация является частью окончательного результата разрешения перегрузки. -  конец примечания  ]

copy-list-initialization с пустым фигурным-init-list {}для неагрегатов запрещает использование явных конструкторов по умолчанию; например:

struct Foo {
    virtual void notAnAggregate() const {};
    explicit Foo() {}
};

void foo(Foo) {}

int main() {
    Foo f1{};    // OK: direct-list-initialization

    // Error: converting to 'Foo' from initializer
    // list would use explicit constructor 'Foo::Foo()'
    Foo f2 = {};
    foo({});
}

Хотя стандартная цитата выше относится к C++17, это также относится к C++11, C++14 и C++20.


Случай № 2 [толькоC++17]: тип класса с объявленным пользователем конструктором, помеченный как explicit не является совокупностью

[dcl.init.aggr]/1 добавлен был обновлен между C++ 14 и C++17, в основном за счет разрешения общего наследования агрегата от базового класса, с некоторыми ограничениями, но также запрещающих explicitконструкторы для агрегатов [выделено мной ]:

Агрегат представляет собой массив или класс с

  • (1.1) не предоставляется пользователем, explicit, или унаследованные конструкторы ([class.ctor]),
  • (1.2) нет частных или защищенных нестатических членов данных (пункт [class.access]),
  • (1.3) нет виртуальных функций, и
  • (1.4) нет виртуальных, частных или защищенных базовых классов ([class.mi]).

Начиная с P1008R1 (Запрет агрегатов с конструкторами, объявленными пользователем), который был реализован для C++20, мы больше не можем объявлять конструкторы для агрегатов. Однако в одном только C++17 у нас было своеобразное правило: от того, помечен ли объявленный пользователем (но не предоставленный пользователем) конструктор явным, зависит, был ли тип класса агрегатным или нет. Например, типы классов

struct Foo {
    Foo() = default;
};

struct Bar {
    explicit Bar() = default;
};

были агрегатами / не агрегатами в C++11 - C++20 следующим образом:

  • С ++11: Foo & Bar оба агрегата
  • С ++14: Foo & Bar оба агрегата
  • C++17: Только Foo является совокупностью (Bar имеет explicit конструктор)
  • C++20: Ни один из Foo или же Bar являются агрегатами (оба имеют конструкторы, объявленные пользователем)
Другие вопросы по тегам