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;
Angew's and jaggedSpire's' answers are excellent and apply to c++11. And c++14. And c++17.
However, in c++20, things change a bit and the example in the OP will no longer compile:
class C {
C() = default;
};
C p; // always error
auto q = C(); // always error
C r{}; // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20
As pointed out by the two answers, the reason the latter two declarations work is because C is an aggregate and this is aggregate-initialization. However, as a result of P1008 (using a motivating example not too dissimilar from the OP), the definition of aggregate changes in C++20 to, from [dcl.init.aggr]/1:
An aggregate is an array or a class ([class]) with
no user-declared or inherited constructors ([class.ctor]),
no private or protected direct non-static data members ([class.access]),
no virtual functions ([class.virtual]), and
no virtual, private, or protected base classes ([class.mi]).
Emphasis mine. Now the requirement is no user-declared constructors, whereas it used to be (as both users cite in their answers and can be viewed historically for C++11, C++14, and C++17) no user-provided constructors. The default constructor for C is user-declared, but not user-provided, and hence ceases to be an aggregate in C++20.
Here is another illustrative example of aggregate changes:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
B was not an aggregate in C++11 or C++14 because it has a base class. As a result, B{} just invokes the default constructor (user-declared but not user-provided), which has access to A's protected default constructor.
In C++17, as a result of P0017, aggregates were extended to allow for base classes. B is an aggregate in C++17, which means that B{} is aggregate-initialization that has to initialize all the subobjects - including the A subobject. But because A's default constructor is protected, we don't have access to it, so this initialization is ill-formed.
In C++20, because of B's user-declared constructor, it again ceases to be an aggregate, so B{} reverts to invoking the default constructor and this is again well-formed initialization.
Related
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;
Angew's and jaggedSpire's' answers are excellent and apply to c++11. And c++14. And c++17.
However, in c++20, things change a bit and the example in the OP will no longer compile:
class C {
C() = default;
};
C p; // always error
auto q = C(); // always error
C r{}; // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20
As pointed out by the two answers, the reason the latter two declarations work is because C is an aggregate and this is aggregate-initialization. However, as a result of P1008 (using a motivating example not too dissimilar from the OP), the definition of aggregate changes in C++20 to, from [dcl.init.aggr]/1:
An aggregate is an array or a class ([class]) with
no user-declared or inherited constructors ([class.ctor]),
no private or protected direct non-static data members ([class.access]),
no virtual functions ([class.virtual]), and
no virtual, private, or protected base classes ([class.mi]).
Emphasis mine. Now the requirement is no user-declared constructors, whereas it used to be (as both users cite in their answers and can be viewed historically for C++11, C++14, and C++17) no user-provided constructors. The default constructor for C is user-declared, but not user-provided, and hence ceases to be an aggregate in C++20.
Here is another illustrative example of aggregate changes:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
B was not an aggregate in C++11 or C++14 because it has a base class. As a result, B{} just invokes the default constructor (user-declared but not user-provided), which has access to A's protected default constructor.
In C++17, as a result of P0017, aggregates were extended to allow for base classes. B is an aggregate in C++17, which means that B{} is aggregate-initialization that has to initialize all the subobjects - including the A subobject. But because A's default constructor is protected, we don't have access to it, so this initialization is ill-formed.
In C++20, because of B's user-declared constructor, it again ceases to be an aggregate, so B{} reverts to invoking the default constructor and this is again well-formed initialization.
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;
Angew's and jaggedSpire's' answers are excellent and apply to c++11. And c++14. And c++17.
However, in c++20, things change a bit and the example in the OP will no longer compile:
class C {
C() = default;
};
C p; // always error
auto q = C(); // always error
C r{}; // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20
As pointed out by the two answers, the reason the latter two declarations work is because C is an aggregate and this is aggregate-initialization. However, as a result of P1008 (using a motivating example not too dissimilar from the OP), the definition of aggregate changes in C++20 to, from [dcl.init.aggr]/1:
An aggregate is an array or a class ([class]) with
no user-declared or inherited constructors ([class.ctor]),
no private or protected direct non-static data members ([class.access]),
no virtual functions ([class.virtual]), and
no virtual, private, or protected base classes ([class.mi]).
Emphasis mine. Now the requirement is no user-declared constructors, whereas it used to be (as both users cite in their answers and can be viewed historically for C++11, C++14, and C++17) no user-provided constructors. The default constructor for C is user-declared, but not user-provided, and hence ceases to be an aggregate in C++20.
Here is another illustrative example of aggregate changes:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
B was not an aggregate in C++11 or C++14 because it has a base class. As a result, B{} just invokes the default constructor (user-declared but not user-provided), which has access to A's protected default constructor.
In C++17, as a result of P0017, aggregates were extended to allow for base classes. B is an aggregate in C++17, which means that B{} is aggregate-initialization that has to initialize all the subobjects - including the A subobject. But because A's default constructor is protected, we don't have access to it, so this initialization is ill-formed.
In C++20, because of B's user-declared constructor, it again ceases to be an aggregate, so B{} reverts to invoking the default constructor and this is again well-formed initialization.
In the following code, compiled with Clang 8.0.0+ and -std=c++17, creating a derived class instance using B{} gives an error error: temporary of type 'A' has protected destructor. Why is A appearing in this message when the temporary has type B (and thus should have a public destructor)?
https://godbolt.org/z/uOzwYa
class A {
protected:
A() = default;
~A() = default;
};
class B : public A {
// can also omit these 3 lines with the same result
public:
B() = default;
~B() = default;
};
void foo(const B&) {}
int main() {
// error: temporary of type 'A' has protected destructor
foo(B{});
// ^
return 0;
}
This is a subtle issue of aggregate initialization before C++20.
Before C++20, B (and A) are aggregate types:
(emphasis mine)
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed) (since C++17) (until C++20)
Then
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates).
So B{} constructs a temporary object via aggregate initialization, which will initialize the base subobject directly with empty list, i.e. perform aggregate initialization to construct the A base subobject. Note that the constructor of B is bypassed. The problem is that in such context, the protected desctructor can't be called to destroy the directly constructed base subobject of type A. (It doesn't complain about the protected constructor because it's bypassed by the aggregate initialization of A too.)
You can change it to foo(B()); to avoid aggregate initialization; B() performs value-initialization, the temporary object will be initialized by B's constructor, then anything is fine.
BTW since C++20 you code will work fine.
no user-declared or inherited constructors (since C++20)
B (and A) are not aggregate types again. B{} performes list initialization, and then the temporary object is initialized by B's constructor; the effect is just same as B().
class B {
private:
friend class C;
B() = default;
};
class C : public B {};
class D : public B {};
int main() {
C {};
D {};
return 0;
}
I assumed that since only class C is a friend of B, and B's constructor is private, then only class C is valid and D is not allowed to instantiate B. But that's not how it works. Where am I wrong with my reasoning, and how to achieve this kind of control over which classes are allowed to subclass a certain base?
Update: as pointed out by others in the comments, the snippet above works as I initially expected under C++14, but not C++17. Changing the instantiation to C c; D d; in main() does work as expected in C++17 mode as well.
This is a new feature added to C++17. What is going on is C is now considered an aggregate. Since it is an aggregate, it doesn't need a constructor. If we look at [dcl.init.aggr]/1 we get that an aggregate is
An aggregate is an array or a class with
no user-provided, explicit, or inherited constructors ([class.ctor]),
no private or protected non-static data members (Clause [class.access]),
no virtual functions, and
no virtual, private, or protected base classes ([class.mi]).
[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors. — end note ]
And we check of all those bullet points. You don't have any constructors declared in C or D so there is bullet 1. You don't have any data members so the second bullet doesn't matter, and your base class is public so the third bullet is satisfied.
The change that happened between C++11/14 and C++17 that allows this is that aggregates can now have base classes. You can see the old wording here where it expressly stated that bases classes are not allowed.
We can confirm this by checking the trait std::is_aggregate_v like
int main()
{
std::cout << std::is_aggregate_v<C>;
}
which will print 1.
Do note that since C is a friend of B you can use
C c{};
C c1;
C c2 = C();
As valid ways to initialize a C. Since D is not a friend of B the only one that works is D d{}; as that is aggregate initialization. All of the other forms try to default initialize and that can't be done since D has a deleted default constructor.
From What is the default access of constructor in c++:
If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted. An implicitly-declared default constructor is an inline public member of its class.
If the class definition does not explicitly declare a copy constructor, one is declared implicitly. [...] An implicitly-declared copy/move constructor is an inline public member of its class.
Constructors for classes C and D are generated internally by compiler.
BTW.: If you want to play with inheritance, please make sure you have virtual destructor defined.
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;
Angew's and jaggedSpire's' answers are excellent and apply to c++11. And c++14. And c++17.
However, in c++20, things change a bit and the example in the OP will no longer compile:
class C {
C() = default;
};
C p; // always error
auto q = C(); // always error
C r{}; // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20
As pointed out by the two answers, the reason the latter two declarations work is because C is an aggregate and this is aggregate-initialization. However, as a result of P1008 (using a motivating example not too dissimilar from the OP), the definition of aggregate changes in C++20 to, from [dcl.init.aggr]/1:
An aggregate is an array or a class ([class]) with
no user-declared or inherited constructors ([class.ctor]),
no private or protected direct non-static data members ([class.access]),
no virtual functions ([class.virtual]), and
no virtual, private, or protected base classes ([class.mi]).
Emphasis mine. Now the requirement is no user-declared constructors, whereas it used to be (as both users cite in their answers and can be viewed historically for C++11, C++14, and C++17) no user-provided constructors. The default constructor for C is user-declared, but not user-provided, and hence ceases to be an aggregate in C++20.
Here is another illustrative example of aggregate changes:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
B was not an aggregate in C++11 or C++14 because it has a base class. As a result, B{} just invokes the default constructor (user-declared but not user-provided), which has access to A's protected default constructor.
In C++17, as a result of P0017, aggregates were extended to allow for base classes. B is an aggregate in C++17, which means that B{} is aggregate-initialization that has to initialize all the subobjects - including the A subobject. But because A's default constructor is protected, we don't have access to it, so this initialization is ill-formed.
In C++20, because of B's user-declared constructor, it again ceases to be an aggregate, so B{} reverts to invoking the default constructor and this is again well-formed initialization.