Martin Fehrs
Martin Fehrs

Reputation: 1091

Why elements of std::initializer_list have to be copied?

cppreference says:

The underlying array is a temporary array of type const T[N], in which each element is copy-initialized (except that narrowing conversions are invalid) from the corresponding element of the original initializer list. The lifetime of the underlying array is the same as any other temporary object, except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary (with the same exceptions, such as for initializing a non-static class member). The underlying array may be allocated in read-only memory.

What's the reasoning behind this decision? Why is moving not ok?

What about copy-ellision?

struct A { A(const A&){ std::cout << "Oh no, a copy!\n"; } };
struct B { B(std::initializer_list<A> il); };

int main()
{
    B b{ A{} };
    return 0;
}

My compiler ellides the copy. But are these copies guaranteed to be ellided?

Upvotes: 2

Views: 909

Answers (1)

"Copy initialization" in C++ doesn't mean things will necessarily be copied. It's just a formal name for the constraints under which initialization will occur. For instance, when copy initializing, explicit c'tors are not candidates. So the following code will be ill-formed

#include <iostream>
#include <initializer_list>

struct A {
    explicit A() = default;
    A(const A&){ std::cout << "Oh no, a copy!\n"; } 
};
struct B { B(std::initializer_list<A> il); };

int main()
{
    B b{ {} };
    return 0;
}

The single member in the list needs to be copy-initialized from {}, which entails calling the default c'tor. However, since the c'tor is marked explicit, this initialization cannot happen.

Copy elision is certainly possible pre-C++17, and is mandatory in C++17 onward in certain contexts. In your example, under a C++17 compiler, since you provide an initializer that is a prvalue (a pure rvalue, not an object), the initialization rules of C++ mandate that the target is initialized directly, without intermediate objects created. Even though the context is called "copy initialization", there are no superfluous objects.

Upvotes: 2

Related Questions