Let's say I have a type and I want to make its default constructor private. I write the following:
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
}
Great.
But then, the constructor turns out to not be as private as I thought it was:
class C {
C() = default;
};
int main() {
C c{}; // OK on all compilers
auto c2 = C{}; // OK on all compilers
}
This strikes me as very surprising, unexpected, and explicitly undesired behavior. Why is this OK?
The trick is in C++14 8.4.2/5 [dcl.fct.def.default]:
... A function is user-provided if it is user-declared and not explicitly defaulted or
deleted on its first declaration. ...
Which means that C
's default constructor is actually not user-provided, because it was explicitly defaulted on its first declaration. As such, C
has no user-provided constructors and is therefore an aggregate per 8.5.1/1 [dcl.init.aggr]:
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
You're not calling the default constructor, you're using aggregate initialization on an aggregate type. Aggregate types are allowed to have a defaulted constructor, so long as it's defaulted where it's first declared:
From [dcl.init.aggr]/1:
An aggregate is an array or a class (Clause [class]) with
- no user-provided constructors ([class.ctor]) (including those inherited ([namespace.udecl]) from a base class),
- no private or protected non-static data members (Clause [class.access]),
- no virtual functions ([class.virtual]), and
- no virtual, private, or protected base classes ([class.mi]).
and from [dcl.fct.def.default]/5
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
Thus, our requirements for an aggregate are:
- no non-public members
- no virtual functions
- no virtual or non-public base classes
- no user-provided constructors inherited or otherwise, which allows only constructors which are:
- implicitly declared, or
- explicitly declared and defined as defaulted at the same time.
C
fulfills all of these requirements.
Naturally, you may be rid of this false default construction behavior by simply providing an empty default constructor, or by defining the constructor as default after declaring it:
class C {
C(){}
};
// --or--
class C {
C();
};
inline C::C() = default;