Uniform Initialization Fails to Copy When Object Has No Data Members

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



Leave a reply



Submit