Необязательный характер initializer_list приводит к чрезмерному копированию

Почему доступ к std::initializer_list не позволяет нам изменять его содержимое? Это большой недостаток std::initializer_list при его использовании для его основной цели (для инициализации контейнера), поскольку он использует избыточное копирование/копирование, вместо move-construction/move-assign.

#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;
}

Выход (как ожидалось):

A::A(const A &)
A::A(const A &)
A::A(const A &)

Почему его конструкция настолько ограничена?

Ответ 1

Существует недавнее предложение для перемещаемых списков инициализаций, где, в частности, авторы говорят:

std::initializer_list был разработан около 2005 года (N1890) до 2007 года (N2215), до сдвинуть семантику, созревшую в 2009 году. В то время не предполагалось, что семантика копирования будет недостаточным или даже субоптимальным для общих классов, подобных значению. Был 2008 год Предложение N2801 Инициализатор перечисляет и перемещает семантику, но С++ 0x уже чувствовал, что он ускользает в то время, и к 2011 году дело стало холодным.

Ответ 2

хороший (если несчастливый) ответ Антона.

Здесь исходный код реализации в libС++:

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());
    }
}

нет движущихся итераторов в поле зрения, поэтому постройте копию.

если это полезно, здесь обходной путь с использованием списка вариационных аргументов:

#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;
}