Aggregate initialization vs uniform initialization - c++

The more I read about C++11 uniform initialization, the more confused I am.
Scott Meyers in Effective Modern C++ (page 55) says that the statement
Widget w2{};
always calls the default constructor (even in the presence of constructor with a std::initializer_list argument).
At first sight, this seems to be consistent with the 4th edition of Stroustrup book "C++ programing language", e.g. according to the table on page 1200 the statement
std::atomic<T> x;
leaves the atomic variable uninitialized, while
std::atomic<T> x {};
calls "default constructor" so that x represents a value initialized T object.
However, I can't believe that std::atomic<T> x; does not call the default constructor in C++11 anymore, so I'm totally confused here.
Finally, after taking a look at C++11 standard (n3337 draft) my confusion is even bigger.
On page 1102 we have:
template <> struct atomic<integral > {
//[...] list of non-constructor functions
atomic() noexcept = default;
constexpr atomic(integral ) noexcept;
atomic(const atomic&) = delete;
//[...] other non-constructor functions
};
While on page 1104 (point 29.5.5) we see
The atomic integral specializations and the specialization
atomic shall have standard layout. They shall each have a
trivial default constructor and a trivial destructor. They shall each
support aggregate initialization syntax.
So classes with user defined constructor now support aggregate initialization?
Is this so because the constuctor is constexpr?
And what happens when we write
std::atomic<T> x {};
Is this the aggregate initialization? Or a call to the (trivial) default constructor?

So classes with user defined constructor now support aggregate initialization?
Only aggregates support aggregate initialization. An aggregate can have a user defined constructor only if the constructor is defined as defaulted or deleted.
This will change in C++20 where no user declared constructors are allowed at all.
Is this so because the constuctor is consexpr?
constexpr has no effect on this.
std::atomic<T> x {};
Is this the aggregate initialization?
This is list initialization. If the type is an aggregate, then list initialization would aggregate initialize the object. std::atomic is not an aggregate, because it has a user-provided constructor that is neither defaulted nor deleted:
constexpr atomic( T desired ) noexcept; // (2) (since C++11)
For std::atomic, this rule of list initialization applies:
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
And value initialization invokes the default constructor for such class.

Related

List initialization - What changed in C++14?

These two lines from cppreference
What is the difference between these two statements ? I don't see any difference
until c++14
If the braced-init-list is empty and T is a class type with a default
constructor, value-initialization is performed. Otherwise, if T is an
aggregate type, aggregate initialization is performed.
since c++14
If T is an aggregate type, aggregate initialization is performed.
Otherwise, if the braced-init-list is empty and T is a class type with
a default constructor, value-initialization is performed.
The difference is which one happens when both conditions apply: if T is an aggregate class (as opposed to an array), which certainly has a default constructor, and the braced-init-list is empty. Of course, to understand why that matters, we then have to distinguish value initialization from aggregate initialization from an empty list.
Value initialization zero-initializes the object and then default-initializes it, which for an aggregate is default-initializing each of its members, so the value-initialization is member-wise (plus zeroing padding). Aggregate initialization initializes each member from {}, which is again value initialization for many types but is default initialization for members of class type with a user-provided default constructor. The difference can be seen in
struct A {A() {} int i;};
struct B {A a;}; // aggregate
B b{}; // i is 0 in C++11, uninitialized in C++14
B b2=B(); // i is 0 in both versions
In C++14 only, aggregates can have default member initializers; that can't contribute to a difference in behavior between the two language versions, of course, but it doesn't behave differently between these two rules anyway (since it replaces only the common default initialization).
The difference is the sequence of checking, so the aggregate type checking is taking place at the first place, and only then the rest.

Why does aggregate initialization not work anymore since C++20 if a constructor is explicitly defaulted or deleted?

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.

Difference syntax to pass values to a constructor c++

here i am using two syntax to pass values to a constructor :-
class A
{
public:
int x,y;
A(int a,int b) : x(a),y(b){}
void show()
{
cout<<x<<" "<<y<<endl;
}
};
int main()
{
A obj1={5,6};//first method
A obj2(9,10);//second method
obj1.show();
obj2.show();
}
And both are working fine
but when i remove the constructor function itself then also the first method is working fine .
please explain this.
When you remove this constructor, class A becomes eligible for aggregate initialization, which is caused here by this curly braces syntax: A obj1={5,6}; (that also has this equivalent form: A obj1{5,6}; since C++11)
As you can read here, this applies in your case as class A then has none of the following:
private or protected non-static data members (applies until C++11)
user-declared constructors (applies since C++11 until C++17)
user-provided constructors (explicitly defaulted or deleted constructors are allowed) (applies since C++17 until C++20)
user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed) (applies since C++20)
user-declared or inherited constructors
virtual, private, or protected (applies since C++17) base classes
virtual member functions
default member initializers (applies since C++11 until C++14)
In contrast, this syntax: A obj2(9,10); is performing direct-initialization, and it fails to compile once you remove the constructor due to [dcl.init¶17.6.2]:
[...] if the initialization is direct-initialization [...]
constructors are considered. [...] If no constructor applies [...] the initialization is
ill-formed.
Prior to removing the constructor, the same syntax A obj1={5,6}; was calling it, while performing copy-list-initialization.
What is aggregate initialization? It is the initialization of an instance of an aggregate type (either an array or a struct/class adhering to the above list) by the braced-init-list syntax. The arguments inside the braces must match the struct/class non-static data members in declaration order. It got significant boost in later versions of C++, as more syntactic forms for it were added to the language. Your usage of ={...} is the only one in existence from before C++11.
The general pros and cons of using parentheses forms of initialization vs. brace initialization is discussed by many. A good place to get a comprehensive readout is at Item 7 of Scott Meyers' Effective Modern C++. That said, the foremost advantage of all brace initialization forms is that they don't allow narrowing.
A obj1={5,6};
This is using list initialization for the object, which is perfectly valid even without your 2 argument constructor being defined.
This may help you understand: Why is list initialization (using curly braces) better than the alternatives?

Default construction of deleted constructor with braced initializer list

Let's say that I want to disable the construction of class, then I can do the following (as per Best style for deleting all constructors (or other function)?
):
// This results in Example being CopyConstructible:
struct Example {
Example() = delete;
};
or
struct Example {
template <typename... Ts>
Example(Ts&&...) = delete;
};
or
struct Example {
Example(const Example&) = delete;
};
where the first example can still be copied if constructed (which the intention is to disable), but the second two will disable almost all methods for creating Example. If I default construct any of the above using an empty braced initializer list, then the instance is successfully constructed. In Meyer's Effective Modern C++ he gives the example (bottom of page 51):
Widget w3{}; // calls Widget ctor with no args
which I would thus expect to fail for the above Example classes i.e:
Example e{};
should not construct since it should call the deleted default constructor. However, it does, is usable, and if defined as the first case above, is also copyable. See live demo. My question is: Is this correct, and if so, why? Also, if this is correct, how do I completely disable the destruction of the class?
From ground up
We will first clarify what it means to initialize an object and how/when/if a constructor is invoked.
The following is my laymen's interpretation of the standard, for simplicity's sake some irrelevant details have been omitted or mangled.
Initializer
An initializer is one of the following
() // parentheses
// nothing
{} // braced initializer list
= expr // assignment expression
The parentheses and braced initializer list may contain further expressions.
They are used like this, given struct S
new S() // empty parentheses
S s(1, 2) // parentheses with expression list as (1, 2)
S s // nothing
S s{} // empty braced initializer list
S s{{1}, {2}} // braced initializer list with sublists
S s = 1 // assignment
S s = {1, 2} // assignment with braced initializer list
Note that we have not yet mentioned constructors
Initialization
Initialization is performed according to what initializers are used.
new S() // value-initialize
S s(1, 2) // direct-initialize
S s // default-initialize
S s{} // list-initialize
S s{{1}, {2}} // list-initialize
S s = 1 // copy-initialize
S s = {1, 2} // list-initialize
Once initialization is performed, the object is considered initialized.
Note that, again, constructors have not been mentioned
List initialize
We will be primarily explaining what it means to list initialize something, as this is the question at hand.
When list initialization occurs, the following is considered in order
If the object is an aggregate type and the list has a single element that is the object's type or is derived from the object's type, the object is initialized with that element
If the object is an aggregate type, the object is aggregate initialized
If the list is empty, and the object has a default constructor, the object is value-initialized (ends up calling default constructor)
If the object is a class type, the constructors are considered, performing overload resolution with the elements of the list
Aggregate
An aggregate type is defined as [dcl.init.aggr]
An aggregate is an array or a class with
-- no user-provided, explicit, or inherited constructors
-- no private or protected non-static data members
-- no virtual functions, and no virtual, private, or protected base classes
Having a deleted constructor does not count towards providing a constructor.
Elements of an aggregate is defined as
The elements of an aggregate are:
-- for an array, the array elements in increasing subscript order, or
-- for a class, the direct base classes in declaration order, followed by the direct non-static data members that are not members of an anonymous union, in declaration order.
Aggregate-initialization is defined as
[...] the elements of the initializer list are taken as initializers for the elements of the aggregate, in order.
Example e{}
Following the rules above the question why Example e{} is legal is because
the initializer is a braced initializer list
uses list initialization
since Example is an aggregate type
uses aggregate initialization
and therefore does not invoke any constructor
When you write Example e{}, it is not default constructed. It is aggregate initialized. So, yes it is perfectly fine.
In fact, the following compiles
struct S
{
S() = delete;
S(const S&) = delete;
S(S&&) = delete;
S& operator=(const S&) = delete;
};
S s{}; //perfectly legal
Turn off construction
Make sure that Example is not an aggregate type to stop aggregate initialization and delete its constructors.
This is often trivial as most classes have private or protected data members. As such, it is often forgotten that aggregate initialization exists in C++.
The simplest way to make a class non-aggregate would be
struct S
{
explicit S() = delete;
};
S s{}; //illegal, calls deleted default constructor
However, as of 2017 May 30, only gcc 6.1 and above and clang 4.0.0 will reject this, all versions of CL and icc will incorrectly accept this.
Other initializations
This is one of the craziest corners in C++, and it was hellish informative to look through the standard to understand what exactly happened. There have been lots of references already written and I will not attempt to explain them.

C++11 member initializer list vs in-class initializer?

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.