Why does the access to std::initializer_list
not allow us to change its content? It's a big disadvantage of std::initializer_list
when using it for its main purpose (to initialize a container), since it's use leads to excessive copy-construction/copy-assignment, instead of move-construction/move-assignment.
#include <initializer_list>
#include <iostream>
#include <vector>
#include <cstdlib>
struct A
{
A() = default;
A(A const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A(A &&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A & operator = (A const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
A & operator = (A &&) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
};
int
main()
{
std::vector< A >{A{}, A{}, A{}};
return EXIT_SUCCESS;
}
Output (as expected):
A::A(const A &)
A::A(const A &)
A::A(const A &)
Why is its design so constrained?
There is a recent proposal for movable initializer lists, where, in particular, the authors say:
std::initializer_list
was designed around 2005 (N1890) to 2007 (N2215), before
move semantics matured, around 2009. At the time, it was not anticipated that copy semantics
would be insufficient or even suboptimal for common value-like classes. There was a 2008
proposal N2801 Initializer lists and move semantics but C++0x was already felt to be slipping at that time, and by 2011 the case had gone cold.
good (if unfortunate) answer by Anton.
Here's the source code of the implementation in libc++:
template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
vector<_Tp, _Allocator>::vector(initializer_list<value_type> __il)
{
#if _LIBCPP_DEBUG_LEVEL >= 2
__get_db()->__insert_c(this);
#endif
if (__il.size() > 0)
{
allocate(__il.size());
__construct_at_end(__il.begin(), __il.end());
}
}
no move iterators in sight, hence copy construction.
in case it's useful, here's a workaround using a variadic argument list:
#include <initializer_list>
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <utility>
#include <cstdlib>
struct A
{
A() noexcept{ std::cout << __PRETTY_FUNCTION__ << std::endl; }
A(A const &) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A & operator = (A const &) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
A(A &&) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; }
A & operator = (A &&) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
};
template<class T, class...Args>
void append_it(std::vector<T>& v)
{
}
template<class T, class...Args>
void append_it(std::vector<T>& v, T&& t1, Args&&...args)
{
v.push_back(std::move(t1));
append_it(v, std::forward<Args&&>(args)...);
}
template<class T, class...Args>
std::vector<T> make_vector(T&& t1, Args&&...args)
{
std::vector<T> result;
result.reserve(1 + sizeof...(args));
result.push_back(std::move(t1));
append_it(result, std::forward<Args&&>(args)...);
return result;
}
int
main()
{
auto v2 = make_vector( A{}, A{}, A{} );
return EXIT_SUCCESS;
}