Uniform initialization fails to copy when object has no data members
Johannes's answer is useful, but let me elaborate on why it currently happens.
Both of the changes you describe affect your class by making it go from an aggregate to not an aggregate. See C++11 (N3485) § 8.5.1/1:
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
A constructor with a definition of = default
is considered not user-defined.
Then, going down to list-initialization in § 8.5.4, we see:
List-initialization of an object or reference of type T is defined as follows:
- If T is an aggregate, aggregate initialization is performed
And then a bunch of "Otherwise..." sections. Thus, changing either of these allows a constructor to be called instead of performing aggregate initialization.
The new proposed standardese for the list-initialization definition (as viewable in Johannes's link) provides a prioritized case of a single element in the list, and it having a type of (or really close to) the type of the object being initialized. Aggregate initialization would then be top priority after that.
Usage of Uniform Initialization Syntax
In case of simple types, like int in your case, there is no difference. However, initialization of std::vector from STL will be completely different
std::vector<int> v1(3,1); // v1 consists of: 1, 1, 1
std::vector<int> v2{3,1}; // v2 consists of: 3, 1
Have a look at this answer if you want to see why generally brace {} initialization is better, however quoting from Scott Meyer's book Effective Modern C++, which I highly recommend:
[...] So why isn’t this Item
entitled something like “Prefer braced initialization syntax”?
The drawback to braced initialization is the sometimes-surprising behavior that
accompanies it. [...]
Why do non-static data member initializers defeat uniform initialization syntax?
Yes, this is intended by the standard. What you are attempting here is aggregate initialization. Unfortunately, your second foo
is no longer considered an aggregate due to the equal initializer of f
. See 8.5.1 [dcl.init.aggr] (emphasis mine):
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equalinitializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
Because you have an equal initializer for the member f
, you will need to provide a custom constructor to support the syntax you are after:
struct foo
{
int i;
float f = 0;
constexpr foo(int i, float f) : i(i), f(f) { }
};
...
foo bar{ 5, 3.141f }; // now okay
As to why this was specified in the standard, I have no idea.
Uniform initialization on member initializer list error
This seems like a MSVC bug. The difference is that the struct
version is an aggregate, and the class
version is not (on account of the default private access specifier).
The class version is value initialized by {}
. The struct version is aggregate initialized. A conforming compiler should just list initialize condition_
with {}
, because you didn't provide an initializer for it.
But MSVC seems to be stumbling on the fact that members of an aggregate are copy initialized from the corresponding initializer in the initializer list. It seems to check for the copy c'tor, even if it isn't supposed to actually use it.
This is further supported by the fact it knows what to do when an object of the same type is initialized outside of a member initializer list.
C++11 non-static data member uniform initialization fails for pointers to other classes of same base class
Stripping one layer of inheritance:
struct m {
struct state { state *s;
state() : s(0) {};
state(state &s) : s(&s) {}
set(state &s) { this->s = &s; }
};
struct s1 : state {} A; // calls s1(), the default constructor
struct s2 : state {} B // calls s2(), ditto
, C{B} // calls s2(const s2&), the default copy constructor
, D{A}; // calls s2(const s1&)
m() { B.set(A); } // The m() body runs after all the members are constructed.
} M;
You're getting the construction error because your substates declare no constructors so they're getting the compiler-provided defaults, and there is none from a sibling- or base- class reference (the compiler doesn't provide s2(s1&)
or s2(state&)
).
You're getting the wrong superstate for C
because C{B}
invokes the default copy constructor s2(s2&)
, which runs before m()
's body.
Here's what you want instead:
struct m {
struct state { state *s; state() : s(0) {} state(state &s) : s(&s) {} };
struct s1 : state {} A; // default-constructs, fine
struct s2 : state {
s2(state &s) : state(s) {}
s2(s2&s) : state(s) {}
} B // default-constructs
, C{B} // calls s2(s2&), invokes state(state&)
, D{A}; // calls s2(state&)
;
m() : B(A) {};
} M;
When M's constructor runs, first its base classes (there are none) then its members are constructed in declaration order using the specified initializations. There's only one: B(A)
, so all the rest are defaulted. After all the bases and members are constructed, then the object constructor's body runs.
Can't copy a std::vectorstd::functionvoid () using uniform initialization. Is this correct?
This is LWG 2132 which is not yet a Defect Report but there's clear consensus (and implementation experience) to fix it. The standard says that std::function
's constructor will accept any type, so because an initializer-list constructor is always preferred to other constructors if it's viable, your code tries to construct a vector from an std::initializer_list<std::function<void()>>
with a single element initialized from the object a
. That then causes an error because although you can construct a std::function<void()>
from a
the resulting object isn't callable.
In other words the issue is that std::function
has an unconstrained template constructor allowing conversion from any type. That causes a problem in your case because initializer-list constructors are preferred to other constructors if viable, and the unconstrained function
constructor means it's always possible to create an initializer_list<function<void()>>
from any type so an initializer-list constructor is always viable.
The proposed resolution to 2132 prevents constructing a std::function
from a non-callable type, so the initializer-list constructor isn't viable and the vector
copy constructor is called instead. I implemented that resolution for GCC 4.8, and it's already implemented in Clang's libc++ library too.
Why some types does not work with uniform initialization syntax?
"Uniform initialization" is a non-standard term which was used somewhat unfortunately in promotion of the initializer-list feature during its proposal phase.
No, you can't use it everywhere. In my experience, it's best to restrict it to
- aggregates (with no constructor; C++98 already allowed this but C++11 extends support)
- sequences (
initializer_list
) return
by value expressions calling a non-explicit constructor
Blindly changing everything and expecting no semantic change is just argumentum ad novitatem — doing something because it's new and different, not because it's appropriate.
As for generic programming, yes it's hard to correctly support situations spanning the above categories. Post specific complaints to the message board at http://isocpp.org and perhaps the guys in charge of the language will work harder to restore the generic order which "uniform initialization" was supposed to improve, not aggravate :v) .
Uniform- or direct-initialization when initializing?
To be clear the ambiguity arises for types having multiple constructors, including one taking an std::initializer_list
, and another one whose parameters (when initialized with braces) may be interpreted as an std::initializer_list
by the compiler. That is the case, for instance, with std::vector<int>
:
template<typename T>
struct X1
{
template<typename... Args>
X1(Args&&... args)
: t(std::forward<Args>(args)...) {}
T t;
};
template<typename T>
struct X2
{
template<typename... Args>
X2(Args&&... args)
: t{std::forward<Args>(args)...} {}
T t;
};
int main() {
auto x1 = X1<std::vector<int>> { 42, 2 };
auto x2 = X2<std::vector<int>> { 42, 2 };
std::cout << "size of X1.t : " << x1.t.size()
<< "\nsize of X2.t : " << x2.t.size();
}
(Note that the only difference is braces in X2
members initializer list instead of parenthesis in X1
members initializer list)
Output :
size of X1.t : 42
size of X2.t : 2
Demo
Standard Library authors faced this real problem when writing utility templates such as std::make_unique
, std::make_shared
or std::optional<>
(that are supposed to perfectly forward for any type) : which initialization form is to be preferred ? It depends on client code.
There is no good answer, they usually go with parenthesis (ideally documenting the choice, so the caller knows what to expect). Idiomatic modern c++11 is to prefer braced initialization everywhere (it avoids narrowing conversions, avoid c++ most vexing parse, etc..)
A potential workaround for disambiguation is to use named tags, extensively discussed in this great article from Andrzej's C++ blog :
namespace std{
constexpr struct with_size_t{} with_size{};
constexpr struct with_value_t{} with_value{};
constexpr struct with_capacity_t{} with_capacity{};
}
// These contructors do not exist.
std::vector<int> v1(std::with_size, 10, std::with_value, 6);
std::vector<int> v2{std::with_size, 10, std::with_value, 6};
This is verbose, and apply only if you can modify the ambiguous type(s) (e.g. types that expose constructors taking an std::initializer_list
and other constructors whose arguments list maybe converted to an std::initializer list
)
Related Topics
How Serious Is the New/Delete Operator Mismatch Error
Invalid Initialization of Non-Const Reference with C++11 Thread
How to Make an Image Resize to Scale in Qt
Segmentation Fault with Char Array and Pointer in C on Linux
Why Can't I Access a Protected Member from an Instance of a Derived Class
How to Tell Reliably If a Boost Thread Has Exited Its Run Method
How Copy from One Stringstream Object to Another in C++
How to Handle a Transitive Dependency Conflict Using Git Submodules and Cmake
Why Does Pointer to Int Convert to Void* But Pointer to Function Convert to Bool
Why Can't C++11 Move a Noncopyable Functor to a Std::Function
How Disastrous Is Integer Overflow in C++
Error: Cannot Convert 'Const Wchar_T [13]' to 'Lpcstr {Aka Const Char*}' in Assignment
Addition of Two Pointers in C or C++ Not Supported. Why