Когда частный конструктор не является частным конструктором?

Предположим, у меня есть тип, и я хочу, чтобы его конструктор по умолчанию был закрыт. Я пишу следующее:

class C { C() = default; }; int main() { C c; // error: C::C() is private within this context (g++) // error: calling a private constructor of class 'C' (clang++) // error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC) auto c2 = C(); // error: as above } 

Отлично.

Но тогда конструктор оказывается не таким закрытым, как я думал:

 class C { C() = default; }; int main() { C c{}; // OK on all compilers auto c2 = C{}; // OK on all compilers } 

Это поражает меня как очень неожиданное, неожиданное и явно нежелательное поведение. Почему это нормально?

Трюк находится в C ++ 14 8.4.2 / 5 [dcl.fct.def.default]:

… Функция предоставляется пользователем, если она объявлена ​​пользователем и явно не дефолтна или удалена по ее первой декларации. …

Это означает, что конструктор C умолчанию на самом деле не предоставляется пользователям, поскольку он был явно дефолт по его первой декларации. Таким образом, C не имеет конструкторов, предоставляемых пользователем, и поэтому является агрегатом на 8.5.1 / 1 [dcl.init.aggr]:

Агрегат – это массив или class (раздел 9) без конструкторов, предоставляемых пользователем (12.1), без частных или защищенных нестатических данных (раздел 11), без базовых classов (раздел 10) и без виртуальных функций (10.3 ).

Вы не вызываете конструктор по умолчанию, вы используете агрегатную инициализацию по типу агрегата. Агрегатным типам разрешено иметь конструктор с дефолтом, если он не установлен по умолчанию, где он впервые объявлен:

Из [dcl.init.aggr] / 1 :

Агрегатом является массив или class (раздел [class]) с

  • никакие создаваемые пользователем конструкторы ([class.ctor]) (включая унаследованные ([namespace.udecl]) из базового classа),
  • нет частных или защищенных нестатических элементов данных (раздел [class.access]),
  • нет виртуальных функций ([class.virtual]) и
  • нет виртуальных, частных или защищенных базовых classов ([class.mi]).

и из [dcl.fct.def.default] / 5

Явно-дефолтные функции и неявно объявленные функции коллективно называются дефолтными функциями, и реализация должна предоставлять им неявные определения ([class.ctor] [class.dtor], [class.copy]), что может означать их удаление , Функция предоставляется пользователем, если она объявлена ​​пользователем и явно не дефолтна или удалена по ее первой декларации. Предоставляемая пользователем функция явно дефолт (т. Е. Явно дефолт после ее первого объявления) определяется в том месте, где она явно дефолтна; если такая функция неявно определена как удаленная, программа плохо сформирована. [Примечание. Объявление функции по умолчанию после ее первого объявления может обеспечить эффективное выполнение и краткое определение, позволяя стабильному двоичному интерфейсу развиваться база кода. – конечная нота]

Таким образом, наши требования к совокупности:

  • нет непубличных членов
  • нет виртуальных функций
  • нет виртуальных или непубличных базовых classов
  • никакие предоставленные пользователем конструкторы, унаследованные или иным образом, которые допускают только конструкторы, которые:
    • неявно объявлено или
    • явно объявлен и определен как дефолт одновременно.

C выполняет все эти требования.

Естественно, вы можете избавиться от этого ложного поведения по умолчанию, просто предоставив пустой конструктор по умолчанию или указав конструктор по умолчанию после объявления его:

 class C { C(){} }; // --or-- class C { C(); }; inline C::C() = default; 
Давайте будем гением компьютера.