What difference between these ways of initializing object member variables in C++11 ? Is there another way ? which way is better (performance) ?:
class any {
public:
obj s = obj("value");
any(){}
};
Or
class any {
public:
obj s;
any(): s("value"){}
};
Thanks.
No, these are not the same.
The difference between them is the same that applies for direct-initialization vs. copy-initialization, which is subtle but often very confusing.
§12.6.2 [class.base.init]:
The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of 8.5 for direct-initialization. [...]
In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then
— if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;
§8.5 [dcl.init]:
The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
Initializing a non-static data member on a member-initializer-list follows the rules of direct-initialization, which doesn't create intermediate temporaries that need to be moved/copied (if compiled without a copy-elision), neither the type of the data member must be copyable/movable (even if the copy is elided). In addition, a direct-initialization introduces an explicit context, while a copy-initialization is non-explicit (if a constructor selected for the initialization is explicit, the program won't compile).
In other words, the obj s = obj("value"); syntax won't compile if obj is declared as:
struct obj
{
obj(std::string) {}
obj(const obj&) = delete;
};
or:
struct obj
{
obj(std::string) {}
explicit obj(const obj&) {}
};
As a more tangible example, while the below won't compile:
struct any
{
std::atomic<int> a = std::atomic<int>(1); // ill-formed: non-copyable/non-movable
std::atomic<int> b = 2; // ill-formed: explicit constructor selected
};
this one will:
struct any
{
std::atomic<int> a;
std::atomic<int> b{ 2 };
any() : a(1) {}
};
Which way is better (performance) ?
With a copy-elision enabled both have identical performance. With copy-elision disabled, there is an additional copy/move constructor call upon every instantiation when the copy-initialization syntax is used (that obj s = obj("value"); is one of).
Is there another way ?
The brace-or-equal-initializer syntax allows one to perform a direct-list-initialization as well:
class any {
public:
obj s{ "value" };
any() {}
};
Are there any other differences?
Some other differences that are worth mentioning are:
Brace-or-equal-initializer must reside in a header file along with a class declaration.
If both are combined, member-initializer-list takes priority over brace-or-equal-initializer (that is, brace-or-equal-initializer is ignored).
(C++11 only, until C++14) A class that uses brace-or-equal-initializer violates constraints for an aggregate type.
With the brace-or-equal-initializer syntax it's not possible to perform a direct-initialization other than a direct-list-initialization.
Both examples are equivalent.
Though only if the type is copyable or movable (check it for yourself) and NRVO is actually done (any halfway decent compiler will do it as a matter of course).
Though if you had many constructors and constructor-chaining were inappropriate, the first method would allow you not to repeat yourself.
Also, you can use that method to define aggregates with defaults different from aggregate-initialization for (some) members since C++14.
They are the same.
Neither is better than the other in terms of performance, and there is no other way to initialise them.
The benefit of in-class initialisation (the first in your example) is that the order of initialisation is implicit. In initialiser list you have to explicitly state the order - and compilers will warn of out-of-order initialisation if you get the ordering incorrect.
From the standard:
12.6.2.5
nonstatic data members shall be initialized in the order they were declared
in the class definition
If you get the order wrong in your list, GCC will complain:
main.cpp: In constructor 'C::C()':
main.cpp:51:9: warning: 'C::b' will be initialized after
main.cpp:51:6: warning: 'int C::a'
The benefit of initialiser lists is perhaps a matter of taste - the list is explicit, typically in the source file. In-class is implicit (arguably), and is typically in the header file.
Related
Please consider the code with aggregate struct B having a field of class A with a private constructor:
class A { A(int){} friend struct B; };
struct B { A a{1}; };
int main()
{
B b; //ok everywhere, not aggregate initialization
//[[maybe_unused]] B x{1}; //error everywhere
[[maybe_unused]] B y{}; //ok in GCC and Clang, error in MSVC
}
My question is about aggregate initialization of B. Since the initialization takes place on behalf of the calling code (main function here), I expected that it must be denied by the compiler, since A's constructor is private. And indeed the construction B{1} fails in all compilers.
But to my surprise the construction B{} is accepted by both GCC and Clang, demo: https://gcc.godbolt.org/z/7851esv6Y
And only MSVC rejects it with the error error C2248: 'A::A': cannot access private member declared in class 'A'.
Is it a bug in GCC and Clang, or the standard permits them to accept this code?
... with aggregate struct B ...
For completeness, let's begin with noting that B is indeed an aggregate in C++14 through C++20, as per [dcl.init.aggr]/1 (N4861 (March 2020 post-Prague working draft/C++20 DIS)):
An aggregate is an array or a class ([class]) with
(1.1) no user-declared or inherited constructors ([class.ctor]),
(1.2) no private or protected direct non-static data members ([class.access]),
(1.3) no virtual functions ([class.virtual]), and
(1.4) no virtual, private, or protected base classes ([class.mi]).
whereas in C++11, B is disqualified as an aggregate due to violating no brace-or-equal-initializers for non-static data members, a requirement that was removed in C++14.
Thus, as per [dcl.init.list]/3 B x{1} and B y{} are both aggregate initialization:
List-initialization of an object or reference of type T is defined
as follows:
[...]
(3.4) Otherwise, if T is an aggregate, aggregate initialization is performed ([dcl.init.aggr]).
For the former case, B x{1}, the data member a of B is an explicitly initialized element of the aggregate, as per [dcl.init.aggr]/3. This means, as per [dcl.init.aggr]/4, particularly /4.2, that the data member is copy-initialized from the initializer-clause, which would require a temporary A object to be constructed in the context of the aggregate initialization, making the program ill-formed, as the matching constructor of A is private.
B x{1}; // needs A::A(int) to create an A temporary
// that in turn will be used to copy-initialize
// the data member a of B.
If we instead use an A object in the initializer-clause, there is no need to access the private constructor of A in the context of the aggregate initialization, and the program is well-formed.
class A {
public:
static A get() { return {42}; }
private:
A(int){}
friend struct B;
};
struct B { A a{1}; };
int main() {
auto a{A::get()};
[[maybe_unused]] B x{a}; // OK
}
For the latter case, B y{}, as per [dcl.init.aggr]/3.3, the data member a of B is no longer an explicitly initialized element of the aggregate, and as per [dcl.init.aggr]/5, particularly /5.1
For a non-union aggregate, each element that is not an explicitly
initialized element is initialized as follows:
(5.1) If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
[...]
and the data member a of B is initialized from its default member initializer, meaning the private constructor A::A(int) is no longer accessed from a context where it is not accessible.
Finally, the case of the private destructor
If we add private destructor to A then all compilers demonstrate it with the correct error:
is governed by [dcl.init.aggr]/8 [emphasis mine]:
The destructor for each element of class type is potentially invoked ([class.dtor]) from the context where the aggregate initialization occurs. [ Note: This provision ensures that destructors can be called for fully-constructed subobjects in case an exception is thrown ([except.ctor]). — end note ]
In my opinion, the GCC and CLANG are behaving correctly, but the MVSC is not, for the following reasons:
As you already mentioned struct B is an aggregate and so aggregate initialization takes place when using list-initialization for B (list initialization
Since the initializer list is empty default member initialization is used (aggregate initialization
Since B is a friend of A using the private constuctor of A for default member initialization is allowed
Just for the case it was not clear for you (at least it was not clear for me). The line B x{1}; results in a compiler error, because the compiler tries to find a way to convert the integer 1 in the initializer list into an instance of A before performing copy initialization of the member a of B. But there is no way to perform that conversion at that place, since the constructor of A is private. That's the reason why you get that compiler error
I'm migrating a C++ Visual Studio Project from VS2017 to VS2019.
I'm getting an error now, that didn't occur before, that can be reproduced with these few lines of code:
struct Foo
{
Foo() = default;
int bar;
};
auto test = Foo { 0 };
The error is
(6): error C2440: 'initializing': cannot convert from
'initializer list' to 'Foo'
(6): note: No constructor could take the source type, or
constructor overload resolution was ambiguous
The project is compiled with /std:c++latest flag. I reproduced it on godbolt. If I switch it to /std:c++17, it compiles fine as before.
I tried to compile the same code with clang with -std=c++2a and got a similar error. Also, defaulting or deleting other constructors generates this error.
Apparently, some new C++20 features were added in VS2019 and I'm assuming the origin of this issue is described in https://en.cppreference.com/w/cpp/language/aggregate_initialization.
There it says that an aggregate can be a struct that (among other criteria) has
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed) (since C++17) (until C++20)
no user-declared or inherited constructors (since C++20)
Note that the part in parentheses "explicitly defaulted or deleted constructors are allowed" was dropped and that "user-provided" changed to "user-declared".
So my first question is, am I right assuming that this change in the standard is the reason why my code compiled before but does not anymore?
Of course, it's easy to fix this: Just remove the explicitly defaulted constructors.
However, I have explicitly defaulted and deleted very many constructors in all of my projects because I found it was a good habit to make code much more expressive this way because it simply results in fewer surprises than with implicitly defaulted or deleted constructors. With this change however, this doesn't seem like such a good habit anymore...
So my actual question is:
What is the reasoning behind this change from C++17 to C++20? Was this break of backwards compatibility made on purpose? Was there some trade off like "Ok, we're breaking backwards compatibility here, but it's for the greater good."? What is this greater good?
The abstract from P1008, the proposal that led to the change:
C++ currently allows some types with user-declared constructors to be initialized via aggregate initialization, bypassing those constructors. The result is code that is surprising, confusing, and buggy. This paper proposes a fix that makes initialization semantics in C++ safer, more uniform,and easier to teach. We also discuss the breaking changes that this fix introduces.
One of the examples they give is the following.
struct X {
int i{4};
X() = default;
};
int main() {
X x1(3); // ill-formed - no matching c’tor
X x2{3}; // compiles!
}
To me, it's quite clear that the proposed changes are worth the backwards-incompatibility they bear. And indeed, it doesn't seem to be good practice anymore to = default aggregate default constructors.
The reasoning from P1008 (PDF) can be best understood from two directions:
If you sat a relatively new C++ programmer down in front of a class definition and ask "is this an aggregate", would they be correct?
The common conception of an aggregate is "a class with no constructors". If Typename() = default; is in a class definition, most people will see that as having a constructor. It will behave like the standard default constructor, but the type still has one. That is the broad conception of the idea from many users.
An aggregate is supposed to be a class of pure data, able to have any member assume any value it is given. From that perspective, you have no business giving it constructors of any kind, even if you defaulted them. Which brings us to the next reasoning:
If my class fulfills the requirements of an aggregate, but I don't want it to be an aggregate, how do I do that?
The most obvious answer would be to = default the default constructor, because I'm probably someone from group #1. Obviously, that doesn't work.
Pre-C++20, your options are to give the class some other constructor or to implement one of the special member functions. Neither of these options are palatable, because (by definition) it's not something you actually need to implement; you're just doing it to make some side effect happen.
Post-C++20, the obvious answer works.
By changing the rules in such a way, it makes the difference between an aggregate and non-aggregate visible. Aggregates have no constructors; so if you want a type to be an aggregate, you don't give it constructors.
Oh, and here's a fun fact: pre-C++20, this is an aggregate:
class Agg
{
Agg() = default;
};
Note that the defaulted constructor is private, so only people with private access to Agg can call it... unless they use Agg{}, bypasses the constructor and is perfectly legal.
The clear intent of this class is to create a class which can be copied around, but can only get its initial construction from those with private access. This allows forwarding of access controls, as only code which was given an Agg can call functions that take Agg as a parameter. And only code with access to Agg can create one.
Or at least, that's how it is supposed to be.
Now you could fix this more targetedly by saying that it's an aggregate if the defaulted/deleted constructors are not publicly declared. But that feels even more in-congruent; sometimes, a class with a visibly declared constructor is an aggregate and sometimes it isn't, depending on where that visibly declared constructor is.
Towards a less surprising aggregate in C++20
To be on the same page with all readers, lets start by mentioning that aggregate class types make up a special family of class types that can be, particularly, initialized by means of aggregate initialization, using direct-list-init or copy-list-init, T aggr_obj{arg1, arg2, ...} and T aggr_obj = {arg1, arg2, ...}, respectively.
The rules governing whether a class is an aggregate or not are not entirely straight-forward, particularly as the rules have been changing between different releases of the C++ standard. In this post we’ll go over these rules and how they have changed over the standard release from C++11 through C++20.
Before we visit the relevant standard passages, consider the implementation of the following contrived class type:
namespace detail {
template <int N>
struct NumberImpl final {
const int value{N};
// Factory method for NumberImpl<N> wrapping non-type
// template parameter 'N' as data member 'value'.
static const NumberImpl& get() {
static constexpr NumberImpl number{};
return number;
}
private:
NumberImpl() = default;
NumberImpl(int) = delete;
NumberImpl(const NumberImpl&) = delete;
NumberImpl(NumberImpl&&) = delete;
NumberImpl& operator=(const NumberImpl&) = delete;
NumberImpl& operator=(NumberImpl&&) = delete;
};
} // namespace detail
// Intended public API.
template <int N>
using Number = detail::NumberImpl<N>;
where the design intent has been to create a non-copyable, non-movable singleton class template which wraps its single non-type template parameter into a public constant data member, and where the singleton object for each instantiation is the only that can ever be created for this particular class specialization. The author has defined an alias template Number solely to prohibit users of the API to explicitly specialize the underlying detail::NumberImpl class template.
Ignoring the actual usefulness (or, rather, uselessness) of this class template, have the author correctly implemented its design intent? Or, in other words, given the function wrappedValueIsN below, used as an acceptance test for the design of the publicly intended Number alias template, will the function always return true?
template <int N>
bool wrappedValueIsN(const Number<N>& num) {
// Always 'true', by design of the 'NumberImpl' class?
return N == num.value;
}
We will answer this question assuming that no user abuses the interface by specializing the semantically hidden detail::NumberImpl, in which case the answer is:
C++11: Yes
C++14: No
C++17: No
C++20: Yes
The key difference is that the class template detail::NumberImpl (for any non-explicit specialization of it) is an aggregate in C++14 and C++17, whereas it is not an aggregate in C++11 and C++20. As covered above, initialization of an object using direct-list-init or copy-list-init will result in aggregate initialization if the object is of an aggregate type. Thus, what may look like value-initialization (e.g. Number<1> n{} here)—which we may expect will have the effect of zero-initialization followed by default-initialization as a user-declared but not user-provided default constructer exists—or direct-initialization (e.g. Number<1>n{2} here) of a class type object will actually bypass any constructors, even deleted ones, if the class type is an aggregate.
struct NonConstructible {
NonConstructible() = delete;
NonConstructible(const NonConstructible&) = delete;
NonConstructible(NonConstructible&&) = delete;
};
int main() {
//NonConstructible nc; // error: call to deleted constructor
// Aggregate initialization (and thus accepted) in
// C++11, C++14 and C++17.
// Rejected in C++20 (error: call to deleted constructor).
NonConstructible nc{};
}
Thus, we can fail the wrappedValueIsN acceptance test in C++14 and C++17 by bypassing the private and deleted user-declared constructors of detail::NumberImpl by means of aggregate initialization, specifically where we explicitly provide a value for the single value member thus overriding the designated member initializer (... value{N};) that otherwise sets its value to N.
constexpr bool expected_result{true};
const bool actual_result =
wrappedValueIsN(Number<42>{41}); // false
// ^^^^ aggr. init. int C++14 and C++17.
Note that even if detail::NumberImpl were to declare a private and explicitly defaulted destructor (~NumberImpl() = default; with private access specifyer) we could still, at the cost of a memory leak, break the acceptance test by e.g. dynamically allocating (and never deleting) a detail::NumberImpl object using aggregate initialization (wrappedValueIsN(*(new Number<42>{41}))).
But why is detail::NumberImpl an aggregate in C++14 and C++17, and why is it not an aggregate in C++11 and C++20? We shall turn to the relevant standard passages for the different standard versions for an answer.
Aggregates in C++11
The rules governing whether a class is an aggregate or not is covered by [dcl.init.aggr]/1, where we refer to N3337 (C++11 + editorial fixes) for C++11 [emphasis mine]:
An aggregate is an array or a class (Clause [class]) with no
user-provided constructors ([class.ctor]), no
brace-or-equal-initializers for non-static data members
([class.mem]), no private or protected non-static data members (Clause
[class.access]), no base classes (Clause [class.derived]), and no
virtual functions ([class.virtual]).
The emphasized segments are the most relevant ones for the context of this answer.
User-provided functions
The detail::NumberImpl class does declare four constructors, such that it has four user-declared constructors, but it does not provide definitions for any of these constructors; it makes use of explicitly-defaulted and explicitly-deleted function definitions at the constructors’ first declarations, using the default and delete keywords, respectively.
As governed by [dcl.fct.def.default]/4, defining an explicitly-defaulted or explicitly-deleted function at its first declaration does not count as the function being user-provided [extract, emphasis mine]:
[…] A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. […]
Thus, the detail::NumberImpl fulfills the aggregate class requirement regarding having no user-provided constructors.
For the some additional aggregate confusion (which applies in C++11 through C++17), where the explicitly-defaulted definition is provided out-of-line, refer to my other answer here.
Designated member initializers
Albeit the detail::NumberImpl class has no user-provided constructors, it does use a brace-or-equal-initializer (commmonly referred to as a designated member initializer) for the single non-static data member value. This is the sole reason as to why the detail::NumberImpl class is not an aggregate in C++11.
Aggregates in C++14
For C++14, we once again turn to [dcl.init.aggr]/1, now referring to N4140 (C++14 + editorial fixes), which is nearly identical to the corresponding paragraph in C++11, except that the segment regarding brace-or-equal-initializers has been removed [emphasis mine]:
An aggregate is an array or a class (Clause [class]) with no
user-provided constructors ([class.ctor]), no private or protected
non-static data members (Clause [class.access]), no base classes
(Clause [class.derived]), and no virtual functions ([class.virtual]).
Thus, the detail::NumberImpl class fulfills the rules for it to be an aggregate in C++14, thus allowing circumventing all private, defaulted or deleted user-declared constructors by means of aggregate initialization.
We will get back to the consistently emphasized segment regarding user-provided constructors once we reach C++20 in a minute, but we shall first visit some explicit puzzlement in C++17.
Aggregates in C++17
True to its form, the aggregate once again changed in C++17, now allowing an aggregate to derive publicly from a base class, with some restrictions, as well as prohibiting explicit constructors for aggregates. [dcl.init.aggr]/1 from N4659 ((March 2017 post-Kona working draft/C++17 DIS), states [emphasis mine]:
An aggregate is an array or a class with
(1.1) no user-provided, explicit, or inherited constructors ([class.ctor]),
(1.2) no private or protected non-static data members (Clause [class.access]),
(1.3) no virtual functions, and
(1.4) no virtual, private, or protected base classes ([class.mi]).
The segment in about explicit is interesting in the context of this post, as we may further increase the aggregate cross-standard-releases volatility by changing the declaration of the private user-declared explicitly-defaulted default constructor of detail::NumberImpl from:
template <int N>
struct NumberImpl final {
// ...
private:
NumberImpl() = default;
// ...
};
to
template <int N>
struct NumberImpl final {
// ...
private:
explicit NumberImpl() = default;
// ...
};
with the effect that detail::NumberImpl is no longer an aggregate in C++17, whilst still being an aggregate in C++14. Denote this example as (*). Apart from copy-list-initialization with an empty braced-init-list (see more details in my other answer here):
struct Foo {
virtual void fooIsNeverAnAggregate() 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({});
}
the case shown in (*) is the only situation where explicit actually has an effect on a default constructor with no parameters.
Aggregates in C++20
As of C++20, particularly due to the implementation of P1008R1 (Prohibit aggregates with user-declared constructors) most of the frequently surprising aggregate behaviour covered above has been addressed, specifically by no longer allowing aggregates to have user-declared constructors, a stricter requirement for a class to be an aggregate than just prohibiting user-provided constructors. We once again turn to [dcl.init.aggr]/1, now referring to N4861 (March 2020 post-Prague working draft/C++20 DIS), which states [emphasis mine]:
An aggregate is an array or a class ([class]) with
(1.1) no user-declared, or inherited constructors ([class.ctor]),
(1.2) no private or protected non-static data members ([class.access]),
(1.3) no virtual functions ([class.virtual]), and
(1.4) no virtual, private, or protected base classes ([class.mi]).
We may also note that the segment about explicit constructors has been removed, now redundant as we cannot mark a constructor as explicit if we may not even declare it.
Avoiding aggregate surprises
All the examples above relied on class types with public non-static data members, which is commonly considered an anti-pattern for the design of “non-POD-like” classes. As a rule of thumb, if you’d like to avoid designing a class that is unintentionally an aggregate, simply make sure that at least one (typically even all) of its non-static data members is private (/protected). For cases where this for some reason cannot be applied, and where you still don’t want the class to be an aggregate, make sure to turn to the relevant rules for the respective standard (as listed above) to avoid writing a class that is not portable w.r.t. being an aggregate or not over different C++ standard versions.
Actually, MSDN addressed your concern in the below document:
Modified specification of aggregate type
In Visual Studio 2019, under /std:c++latest, a class with any user-declared constructor (for example, including a constructor declared = default or = delete) isn't an aggregate. Previously, only user-provided constructors would disqualify a class from being an aggregate. This change puts additional restrictions on how such types can be initialized.
It is possible to define a struct (a) that has no user-defined constructors, and (b) for which a default constructor cannot be generated. For example, Foo in the below:
struct Baz
{
Baz(int) {}
};
struct Foo
{
int bar;
Baz baz;
};
You can still create instances of Foo using aggregate initialization:
Foo foo = { 0, Baz(0) };
My normal compiler (VS2012) will grudgingly accept this, but it raises 2 warnings:
warning C4510: 'Foo': default constructor could not be generated.
warning C4610: struct 'Foo' can never be instantiated - user defined constructor required
Of course, I've just proved warning #2 wrong--you can still instantiate it using aggregate initialization. The online compilers I've tried are happy enough to accept the above, so I'm guessing VS2012 is just being overly-aggressive with this warning. But I'd like to be sure--is this code ok, or does it technically violate some obscure part of the standard?
The standard explicitly allows cases like Foo in [12.1p4]:
[...] If there is no user-declared constructor for
class X, a constructor having no parameters is implicitly declared as defaulted [...] A defaulted default constructor for class X is defined as
deleted if:
[...]
any potentially constructed subobject, except for a non-static data member with a brace-or-equal-initializer, has class type M (or array
thereof) and either M has no default constructor or overload
resolution (13.3) as applied to M’s default constructor results in an
ambiguity or in a function that is deleted or inaccessible from the
defaulted default constructor
[...]
Baz has no default constructor, so the emphasised part above applies (emphasis mine).
There's nothing 'undefined' or 'ill-formed' about such cases. The implicitly declared default constructor is defined as deleted, that's all. You could do the same thing explicitly, and it would still be just as valid.
The definition for aggregates is in [8.5.1p1]. For C++14, it is:
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).
The 'no user-provided' part allows you to use = delete on all constructors that could possibly be implicitly declared (making them user-declared, but not user-provided) and the class would still be an aggregate, allowing you to use aggregate initialization on it.
As for warning C4610, I've encountered it before myself and reported it. As you can see, it's been fixed in the upcoming version of VC++.
It may be worth mentioning that the example I used in the bug report is taken directly from the standard, where it's treated as well-formed ([12.2p5.4]:
struct S { int mi; const std::pair<int,int>& mp; };
S a { 1, {2,3} };
This is similar to your case, but here, the implicitly declared default constructor is defined as deleted because the class has a non-static member of reference type that has no initializer.
Granted, it's only an example, but I think it's an additional indication that there's nothing wrong with these cases.
That's not actually aggregate initialization, it's uniform, which is only recently supported by VS. This warning is simply them not correctly updating it to reflect that that type can now be uniform initialized.
Aggregates may not have user-defined non-defaulted non-deleted constructors, and the rules for aggregate UDTs are that each member must also be an aggregate. Therefore, Baz is not an aggregate and as a direct result, neither can Foo be.
Suppose the following:
class foo
{
public:
foo() = default;
private:
std::string m_str = "Hello";
};
I am not able to find any documentation on cppreference.com or the C++11 draft that explains what the default constructor will do to m_str here. My natural assumption is that the compiler is smart enough to not initialize m_str in the defaulted constructor if it has already been initialized in-class, but maybe I'm wrong.
Can anyone explain the initialization behavior here? Any guarantees or is this unspecified?
This is well defined and specified in [class.ctor]/5:
[...] The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with no ctor-initializer (12.6.2) and an empty compound-statement. [...]
So the defaulted constructor performs the same action as:
foo() {}
This in turn applies the brace-or-equal initializer to each data member as appropriate (cf. [class.base.init]/8).
The form of initialization you are using for m_str is called brace-or-equal-initializer by the standard.
In the first paragraph under Intializers, the standard states:
8.5 Intializers
1 A declarator can specify an initial value for the identifier being declared. The identifier designates a variable being initialized. The process of initialization described in the remainder of 8.5 applies also to initializations specified by other syntactic contexts, such as the initialization of function parameters with argumet expressions (5.2.2) or the initialization of return values (6.6.3).
initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
And in the section on class members...
9.2 Class members
5 A member can be initialized using a brace-or-equal-initializer.
And in the section on initializing bases and members...
12.6.2 Initializing bases and members
8 In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then
— if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initializedas specified in 8.5
Coming to your class,
foo() = default;
is equivalent to:
foo(){}
When a default constructor with no member initializer list is used, the member data are default initialized. In the default initialization process, for members that have a brace-or-equal-initializer, that form is used to initialize them. In your case,
foo(){}
is equivalent to:
foo() : m_str("Hello") {}
I recently noticed a class in C++0x that calls for an explicit default constructor. However, I'm failing to come up with a scenario in which a default constructor can be called implicitly. It seems like a rather pointless specifier. I thought maybe it would disallow Class c; in favor of Class c = Class(); but that does not appear to be the case.
Some relevant quotes from the C++0x FCD, since it is easier for me to navigate [similar text exists in C++03, if not in the same places]
12.3.1.3 [class.conv.ctor]
A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value initialization (8.5).
It goes on to provide an example of an explicit default constructor, but it simply mimics the example I provided above.
8.5.6 [decl.init]
To default-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
8.5.7 [decl.init]
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
In both cases, the standard calls for the default constructor to be called. But that is what would happen if the default constructor were non-explicit. For completeness sake:
8.5.11 [decl.init]
If no initializer is specified for an object, the object is default-initialized;
From what I can tell, this just leaves conversion from no data. Which doesn't make sense. The best I can come up with would be the following:
void function(Class c);
int main() {
function(); //implicitly convert from no parameter to a single parameter
}
But obviously that isn't the way C++ handles default arguments. What else is there that would make explicit Class(); behave differently from Class();?
The specific example that generated this question was std::function [20.8.14.2 func.wrap.func]. It requires several converting constructors, none of which are marked explicit, but the default constructor is.
This declares an explicit default constructor:
struct A {
explicit A(int a1 = 0);
};
A a = 0; /* not allowed */
A b; /* allowed */
A c(0); /* allowed */
In case there is no parameter, like in the following example, the explicit is redundant.
struct A {
/* explicit is redundant. */
explicit A();
};
In some C++0x draft (I believe it was n3035), it made a difference in the following way:
A a = {}; /* error! */
A b{}; /* alright */
void function(A a);
void f() { function({}); /* error! */ }
But in the FCD, they changed this (though, I suspect that they didn't have this particular reason in mind) in that all three cases value-initialize the respective object. Value-initialization doesn't do the overload-resolution dance and thus won't fail on explicit constructors.
Unless explicitly stated otherwise, all standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.
(This answer focus specifically on explicit default constructors which have no parameters)
Case #1 [C++11 through C++20]: Empty {} copy-list-initialization for non-aggregates prohibits use of explicit default constructors
As governed by [over.match.list]/1 [emphasis mine]:
When objects of non-aggregate class type T are list-initialized such
that [dcl.init.list] specifies that overload resolution is performed
according to the rules in this section, overload resolution selects
the constructor in two phases:
(1.1) Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class T and the argument list
consists of the initializer list as a single argument.
(1.2) If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all
the constructors of the class T and the argument list consists of
the elements of the initializer list.
If the initializer list has no elements and T has a default
constructor, the first phase is omitted. In
copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed. [ Note: This differs from other
situations ([over.match.ctor], [over.match.copy]), where only
converting constructors are considered for copy-initialization. This
restriction only applies if this initialization is part of the final
result of overload resolution. — end note ]
copy-list-initialization with an empty braced-init-list {} for non-aggregates prohibits use of explicit default constructors; e.g.:
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({});
}
Albeit the standard quote above refers to C++17, this likewise applies for C++11, C++14 and C++20.
Case #2 [C++17 only]: A class type with a user-declared constructor that is marked as explicit is not an aggregate
[dcl.init.aggr]/1 added was updated some between C++14 and C++17, mainly by allowing an aggregate to derive publicly from a base class, with some restrictions, but also prohibiting explicit constructors for aggregates [emphasis mine]:
An aggregate is an array or a class with
(1.1) no user-provided, explicit, or inherited constructors ([class.ctor]),
(1.2) no private or protected non-static data members (Clause [class.access]),
(1.3) no virtual functions, and
(1.4) no virtual, private, or protected base classes ([class.mi]).
As of P1008R1 (Prohibit aggregates with user-declared constructors), which has been implemented for C++20, we may no longer ever declare constructors for aggregates. In C++17 alone, however, we had the peculiar rule that whether a user-declared (but not user-provided) constructor was marked explicit decided whether the class type was an aggregate or not. E.g. the class types
struct Foo {
Foo() = default;
};
struct Bar {
explicit Bar() = default;
};
were aggregates/not aggregates in C++11 through C++20 as follows:
C++11: Foo & Bar are both aggregates
C++14: Foo & Bar are both aggregates
C++17: Only Foo is an aggregate (Bar has an explicit constructor)
C++20: None of Foo or Bar are aggregates (both has user-declared constructors)