In short, why the code below behaves like described in the comments?
struct A
{
A() = delete;
//A(const A&) {} // uncommenting this...
};
int main()
{
A a{}; // ... breaks this
//A(); // this fails in either case because of `A() = delete;` only
}
What part of the standard (or at least a page on cppreference) should I look at to understand this?
However, writing A(const A&) = default;
instead of //A(const A&) {}
doesn't break A a{};
. What about this? I think the underlying cause is the same, but a word from who really knows C++ is better than what I think.
The copy constructor and copy-assignment operator are public but deleted. It is a compile-time error to define or call a deleted function. The intent is clear to anyone who understands =default and =delete . You don't have to understand the rules for automatic generation of special member functions.
Default Copy Constructor This is called a default or standard copy constructor. What it does is simply copy data members of the object passed to it to the corresponding data members of the new object.
Implicitly defined default contructor means all data members have default constructors. Therefore, default constructor for each data member is called in order they are declared in and before the composite class constructor body.
A default argument is a value provided in a function declaration that is automatically assigned by the compiler if the calling function doesn't provide a value for the argument. In case any value is passed, the default value is overridden.
Without the user-provided copy constructor, A
is an aggregate. Yes, even though we deleted the default constructor. It's something that was addressed in C++20.
So, prior to C++20, A a{};
is aggregate initialization, and so doesn't use the deleted constructor. When you uncomment the copy constructor, A
stops being an aggregate, thus turning the aggregate initialization into value initialization. So the initialization of a
will attempt to call the deleted constructor.
To divine the meaning of an initializer from the standard, one typically starts at [dcl.init]/16. Going through the bullets, one can find how the properties of the initializer (when matched with the properties of the types in question) will affect the way initialization occurs.
If a given class, say S
, is an aggregate, then
S s{};
is aggregate initialization, not value initialization, and will bypass any constructors of S
, even if they are deleted or private. The result of aggregate initialization is typically value-initialization of the (public(1)) data members of the aggregate.
(1) As shown below, a class with any private data member is never an aggregate class, not through any of the C++11 through C++20 standards.
The definition of what is an aggregate class is governed by:
where the typical confusion with aggregates is the weaker requirement of C++11 through C++17 to not have any user-provided(2) constructors, which was made much stricter in C++20 with the requirement to not have any user-declared constructors.
(2) The definition of what counts as user-provided function is governed by [dcl.fct.def.default]/4 in C++11 and [dcl.fct.def.default]/5 in C++14 through C++20; the definition of user-provided is essentially the same in all these language versions.
There are also other changes to the definition of aggregates between C++11, C++14 and C++17 that are not as well-known, but that likewise add to the aggregate confusion prior to C++20; we'll walk through a couple of examples to highlight them.
For each example below; if the class S
of the example is an aggregate (for a particular language version), the following is well-formed:
S s{};
The aggregate rules in all the examples above likewise applies for any kind of constructor (given that they are otherwise applicable, e.g. allows to be explicitly defaulted or deleted), not just S()
.
struct S {};
struct S {
int x;
};
struct S {
int x{42};
};
class S {
int x;
};
Has a private data member.
struct S {
S() = default;
};
struct S {
private:
S() = default;
};
struct S {
S() = delete;
};
struct S {
S();
};
S::S() = default;
An out of line explicitly-defaulted definition counts as user-provided; recalling [dcl.fct.def.default]/4 (C++11) / [dcl.fct.def.default]/5 (C++14, C++17):
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
struct S {
explicit S() = default;
};
explicit
constructor)C++17 adds the requirement that an aggregate may not have an explicit
constructor.
struct S {
int x{42};
S() = delete;
};
C++14 removed the requirement of C++11 for an aggregate to have no default member initializers.
struct S {
int x{42};
explicit S() = delete;
};
explicit
constructor)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With