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

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

#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 &)

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


person Tomilov Anatoliy    schedule 28.11.2014    source источник
comment
Как я могу сделать вывод, {A{}, A{}, A{}} создает все содержимое списка инициализаторов на месте.   -  person Tomilov Anatoliy    schedule 28.11.2014
comment
Также нельзя использовать std::initializer_list для инициализации контейнеров некопируемых объектов.   -  person Tomilov Anatoliy    schedule 30.11.2014


Ответы (2)


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

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

person Anton Savin    schedule 28.11.2014

хороший (хотя и неудачный) ответ Антона.

Вот исходный код реализации на 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());
    }
}

никаких итераторов перемещения не видно, следовательно, копирование конструкции.

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

#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;
}
person Richard Hodges    schedule 28.11.2014