initializer_list и переместить семантику - PullRequest
82 голосов
/ 19 ноября 2011

Могу ли я переместить элементы из std::initializer_list<T>?

#include <initializer_list>
#include <utility>

template<typename T>
void foo(std::initializer_list<T> list)
{
    for (auto it = list.begin(); it != list.end(); ++it)
    {
        bar(std::move(*it));   // kosher?
    }
}

Поскольку std::intializer_list<T> требует особого внимания компилятора и не имеет семантики значений, как обычные контейнеры стандартной библиотеки C ++, яЛучше быть в безопасности, чем сожалеть и спросить.

Ответы [ 7 ]

77 голосов
/ 19 ноября 2011

Нет, это не будет работать так, как задумано; вы все равно получите копии. Я очень удивлен этим, поскольку я думал, что initializer_list существует, чтобы хранить множество временных, пока они не будут move 'd.

begin и end для initializer_list возвращают const T *, поэтому результат move в вашем коде равен T const && - неизменная ссылка на rvalue. Такое выражение не может быть осмысленно перенесено из. Он будет привязан к параметру функции типа T const &, поскольку rvalues ​​связывается с константными ссылками lvalue, и вы все равно увидите семантику копирования.

Вероятно, причина этого в том, что компилятор может выбрать initializer_list статически-инициализированной константой, но, кажется, было бы чётче сделать его тип initializer_list или const initializer_list на усмотрение компилятора, поэтому пользователь не знает, следует ли ожидать const или изменяемый результат от begin и end. Но это только мое внутреннее чувство, возможно, есть веская причина, по которой я ошибаюсь.

Обновление: Я написал предложение ISO для initializer_list поддержки типов только для перемещения. Это только первый черновик, и он еще нигде не реализован, но вы можете увидеть его для более подробного анализа проблемы.

17 голосов
/ 19 ноября 2011
bar(std::move(*it));   // kosher?

Не так, как вы собираетесь.Вы не можете переместить объект conststd::initializer_list предоставляет только const доступ к его элементам.Таким образом, тип it равен const T *.

Ваша попытка вызвать std::move(*it) приведет только к значению l.IE: копия.

std::initializer_list ссылки статическая память.Вот для чего этот класс.Вы не можете переместить из статической памяти, потому что движение подразумевает его изменение.Вы можете только скопировать с него.

2 голосов
/ 01 июня 2017

Я подумал, что было бы поучительно предложить разумную отправную точку для обхода проблемы.

Комментарии inline.

#include <memory>
#include <vector>
#include <array>
#include <type_traits>
#include <algorithm>
#include <iterator>

template<class Array> struct maker;

// a maker which makes a std::vector
template<class T, class A>
struct maker<std::vector<T, A>>
{
  using result_type = std::vector<T, A>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const -> result_type
  {
    result_type result;
    result.reserve(sizeof...(Ts));
    using expand = int[];
    void(expand {
      0,
      (result.push_back(std::forward<Ts>(ts)),0)...
    });

    return result;
  }
};

// a maker which makes std::array
template<class T, std::size_t N>
struct maker<std::array<T, N>>
{
  using result_type = std::array<T, N>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const
  {
    return result_type { std::forward<Ts>(ts)... };
  }

};

//
// delegation function which selects the correct maker
//
template<class Array, class...Ts>
auto make(Ts&&...ts)
{
  auto m = maker<Array>();
  return m(std::forward<Ts>(ts)...);
}

// vectors and arrays of non-copyable types
using vt = std::vector<std::unique_ptr<int>>;
using at = std::array<std::unique_ptr<int>,2>;


int main(){
    // build an array, using make<> for consistency
    auto a = make<at>(std::make_unique<int>(10), std::make_unique<int>(20));

    // build a vector, using make<> because an initializer_list requires a copyable type  
    auto v = make<vt>(std::make_unique<int>(10), std::make_unique<int>(20));
}
2 голосов
/ 07 июля 2014

Это не будет работать, как указано, потому что list.begin() имеет тип const T *, и вы никак не можете отойти от постоянного объекта. Разработчики языка, вероятно, сделали это для того, чтобы списки инициализаторов могли содержать, например, строковые константы, из которых было бы неуместно перемещаться.

Однако, если вы находитесь в ситуации, когда вы знаете, что список инициализатора содержит выражения rvalue (или вы хотите заставить пользователя их писать), то есть хитрость, которая заставит его работать (меня вдохновил ответ Сумант за это, но решение намного проще, чем это). Вам необходимо, чтобы элементы, хранящиеся в списке инициализатора, были не значениями T, а значениями, инкапсулирующими T&&. Тогда даже если эти значения сами по себе являются const квалифицированными, они все равно могут получить изменяемое значение r.

template<typename T>
  class rref_capture
{
  T* ptr;
public:
  rref_capture(T&& x) : ptr(&x) {}
  operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};

Теперь вместо объявления аргумента initializer_list<T> вы объявляете аргумент initializer_list<rref_capture<T> >. Вот конкретный пример, включающий вектор умных указателей std::unique_ptr<int>, для которого определена только семантика перемещения (поэтому сами эти объекты никогда не могут быть сохранены в списке инициализатора); тем не менее, приведенный ниже список инициализаторов компилируется без проблем.

#include <memory>
#include <initializer_list>
class uptr_vec
{
  typedef std::unique_ptr<int> uptr; // move only type
  std::vector<uptr> data;
public:
  uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {}
  uptr_vec(std::initializer_list<rref_capture<uptr> > l)
    : data(l.begin(),l.end())
  {}
  uptr_vec& operator=(const uptr_vec&) = delete;
  int operator[] (size_t index) const { return *data[index]; }
};

int main()
{
  std::unique_ptr<int> a(new int(3)), b(new int(1)),c(new int(4));
  uptr_vec v { std::move(a), std::move(b), std::move(c) };
  std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl;
}

Один вопрос нуждается в ответе: если элементы списка инициализатора должны быть истинными значениями (в данном примере они являются значениями x), обеспечивает ли язык время жизни соответствующих временных значений до точки, где они используются? Честно говоря, я не думаю, что соответствующий раздел 8.5 стандарта решает эту проблему вообще. Однако, читая 1.9: 10, может показаться, что соответствующее полное выражение во всех случаях охватывает использование списка инициализаторов, поэтому я думаю, что нет опасности оборванных ссылок на rvalue.

0 голосов
/ 10 июля 2019

У меня есть намного более простая реализация, которая использует класс-обертку, который действует как тег, чтобы отметить намерение перемещения элементов.Это стоимость времени компиляции.

Класс-обертка предназначен для использования таким же образом, как std::move, просто замените std::move на move_wrapper, но для этого требуется C ++ 17.Для более старых спецификаций вы можете использовать дополнительный метод компоновщика.

Вам нужно написать методы / конструкторы компоновщика, которые принимают классы-оболочки внутри initializer_list и соответственно переместить элементы.

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

Код должен быть самодокументирован.

#include <iostream>
#include <vector>
#include <initializer_list>

using namespace std;

template <typename T>
struct move_wrapper {
    T && t;

    move_wrapper(T && t) : t(move(t)) { // since it's just a wrapper for rvalues
    }

    explicit move_wrapper(T & t) : t(move(t)) { // acts as std::move
    }
};

struct Foo {
    int x;

    Foo(int x) : x(x) {
        cout << "Foo(" << x << ")\n";
    }

    Foo(Foo const & other) : x(other.x) {
        cout << "copy Foo(" << x << ")\n";
    }

    Foo(Foo && other) : x(other.x) {
        cout << "move Foo(" << x << ")\n";
    }
};

template <typename T>
struct Vec {
    vector<T> v;

    Vec(initializer_list<T> il) : v(il) {
    }

    Vec(initializer_list<move_wrapper<T>> il) {
        v.reserve(il.size());
        for (move_wrapper<T> const & w : il) {
            v.emplace_back(move(w.t));
        }
    }
};

int main() {
    Foo x{1}; // Foo(1)
    Foo y{2}; // Foo(2)

    Vec<Foo> v{Foo{3}, move_wrapper(x), Foo{y}}; // I want y to be copied
    // Foo(3)
    // copy Foo(2)
    // move Foo(3)
    // move Foo(1)
    // move Foo(2)
}
0 голосов
/ 26 марта 2017

В текущем стандарте это не разрешено, поскольку уже ответил . Вот еще один обходной путь для достижения чего-то подобного, определяя функцию как переменную вместо взятия списка инициализатора.

#include <vector>
#include <utility>

// begin helper functions

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

// end helper functions

struct S {
  S(int) {}
  S(S&&) {}
};

void bar(S&& s) {}

template <typename T, typename... Args>
void foo(Args&&... args) {
  std::vector<T> args_vec = make_vector<T>(std::forward<Args>(args)...);
  for (auto& arg : args_vec) {
    bar(std::move(arg));
  }
}

int main() {
  foo<S>(S(1), S(2), S(3));
  return 0;
}

Шаблоны Variadic могут обрабатывать ссылки на r-значения надлежащим образом, в отличие от initializer_list.

В этом примере кода я использовал набор небольших вспомогательных функций для преобразования переменных аргумента в вектор, чтобы сделать его похожим на исходный код. Но, конечно, вы можете вместо этого написать рекурсивную функцию с шаблонами переменных.

0 голосов
/ 18 сентября 2013

Рассмотрим идиому in<T>, описанную в cpptruths . Идея состоит в том, чтобы определить lvalue / rvalue во время выполнения и затем вызвать move или copy-construction. in<T> обнаружит rvalue / lvalue, даже если стандартный интерфейс, предоставленный initializer_list, является константной ссылкой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...