Назначение явных конструкторов по умолчанию
Недавно я заметил класс в 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
являются агрегатами (оба имеют конструкторы, объявленные пользователем)