initializer_list - убийца move семантики
От: rg45 СССР  
Дата: 22.10.18 21:03
Оценка: 19 (5)
Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями:

struct MovableNonCopyable
{
  MovableNonCopyable() = default;
  MovableNonCopyable(MovableNonCopyable&&) = default;
};
// . . . 
std::vector<MovableNonCopyable> v { MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };
// error: use of deleted function ‘constexpr MovableNonCopyable::MovableNonCopyable(const MovableNonCopyable&)’
--
Отредактировано 22.10.2018 21:04 rg45 . Предыдущая версия .
Re: initializer_list - убийца move семантики
От: YuriV  
Дата: 23.10.18 07:45
Оценка: +1 -2
Здравствуйте, rg45, Вы писали:

R>Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями:


R>
R>struct MovableNonCopyable
R>{
R>  MovableNonCopyable() = default;
R>  MovableNonCopyable(MovableNonCopyable&&) = default;
R>};
R>// . . . 
R>std::vector<MovableNonCopyable> v { MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };
R>// error: use of deleted function ‘constexpr MovableNonCopyable::MovableNonCopyable(const MovableNonCopyable&)’
R>


Это std::vector<T> требует:

T must meet the requirements of CopyAssignable and CopyConstructible.

а такой код вполне работоспособен:
MovableNonCopyable v[] = { MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };
Re: initializer_list - убийца move семантики
От: Videoman Россия https://hts.tv/
Дата: 23.10.18 08:07
Оценка:
Здравствуйте, rg45, Вы писали:

R>Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями


Ок. Тогда вопрос: как проинициализировать вектор элементом используя перемещение?
Re[2]: initializer_list - убийца move семантики
От: ViTech  
Дата: 23.10.18 09:01
Оценка: +1
Здравствуйте, YuriV, Вы писали:

YV>Это std::vector<T> требует:

YV>

YV>T must meet the requirements of CopyAssignable and CopyConstructible.


Это актуально до C++11.

(Since C++11) (until C++17):

The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type is a complete type and meets the requirements of Erasable, but many member functions impose stricter requirements.

std::vector Template parameters.

Здесь, скорей всего, влияет:

An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type const T.


YV>а такой код вполне работоспособен:

MovableNonCopyable v[] = { MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };

Работоспособен, но конструкторы копирования/перемещения MovableNonCopyable, похоже, не вызываются, потому как тут и не std::initializer_list вовсе, а aggregate initialization.
Пока сам не сделаешь...
Re[2]: initializer_list - убийца move семантики
От: rg45 СССР  
Дата: 23.10.18 09:03
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Здравствуйте, rg45, Вы писали:


R>>Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями


V>Ок. Тогда вопрос: как проинициализировать вектор элементом используя перемещение?


Ну вот, основываясь на том же примере
Автор: rg45
Дата: 23.10.18
:

struct MovableNonCopyable
{
  MovableNonCopyable() = default;
  MovableNonCopyable(MovableNonCopyable&&) = default;
};
// . . . 
std::vector<MovableNonCopyable> v; //{ MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };

MovableNonCopyable t;
v.emplace_back(std::move(t));
v.emplace_back(MovableNonCopyable());
--
Re[2]: initializer_list - убийца move семантики
От: rg45 СССР  
Дата: 23.10.18 09:10
Оценка:
Здравствуйте, YuriV, Вы писали:

YV>Здравствуйте, rg45, Вы писали:


R>>Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями:


R>>
R>>struct MovableNonCopyable
R>>{
R>>  MovableNonCopyable() = default;
R>>  MovableNonCopyable(MovableNonCopyable&&) = default;
R>>};
R>>// . . . 
R>>std::vector<MovableNonCopyable> v { MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };
R>>// error: use of deleted function ‘constexpr MovableNonCopyable::MovableNonCopyable(const MovableNonCopyable&)’
R>>


YV>Это std::vector<T> требует:

YV>

YV>T must meet the requirements of CopyAssignable and CopyConstructible.


Да не вопрос, сделай конструктор копирования доступным в классе, ошибка исчезнет. Но выполняться будет копирование, а не перемещение, как, вероятно кто-то ожидает — вот в этом проблема.

YV>а такой код вполне работоспособен:

YV>
YV>MovableNonCopyable v[] = { MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };
YV>


Потому, что это aggregate initialization, с ним все в порядке, в отличие от list initialization. Они хоть по форме и похожи, путать не рекоммендуется
--
Re[3]: initializer_list - убийца move семантики
От: Videoman Россия https://hts.tv/
Дата: 23.10.18 09:29
Оценка:
Здравствуйте, rg45, Вы писали:

R>
R>MovableNonCopyable t;
R>v.emplace_back(std::move(t));
R>v.emplace_back(MovableNonCopyable());
R>


Не, ну "засадить" опосля я и сам могу. Я имею ввиду именно при инициализации, в конструкторе. Просто у меня часто бывает начальная инициализация вектора одним элементом:
MyClass::MyClass() : m_vec(/* как тут инициализировать ???*/) {} // в списке инициализации

или
func1(func2(Vec(/* как тут инициализировать ???*/))); // в каскаде вызовов

Для себя, кроме как создать функцию и там уже сделать emplace_back, и из нее уже вернуть вектор как R-Value, я ничего удобного не нашел
Re[4]: initializer_list - убийца move семантики
От: rg45 СССР  
Дата: 23.10.18 09:41
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Не, ну "засадить" опосля я и сам могу. Я имею ввиду именно при инициализации, в конструкторе. Просто у меня часто бывает начальная инициализация вектора одним элементом:

V>
V>MyClass::MyClass() : m_vec(/* как тут инициализировать ???*/) {} // в списке инициализации
V>

V>или
V>
V>func1(func2(Vec(/* как тут инициализировать ???*/))); // в каскаде вызовов
V>


Увы и ах, выходит, что никак.

V>Для себя, кроме как создать функцию и там уже сделать emplace_back, и из нее уже вернуть вектор как R-Value, я ничего удобного не нашел


Ну можно еше поизвращаться с initializer_list-ом прокси-объектов. Но в этом варианте свои минусы.
--
Re[5]: initializer_list - убийца move семантики
От: Videoman Россия https://hts.tv/
Дата: 23.10.18 09:58
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>Ну можно еше поизвращаться с initializer_list-ом прокси-объектов. Но в этом варианте свои минусы.


Да, я в курсе. Казалось бы, базовые операции. Какой-то привкус недодела чувствуется.
Re[6]: initializer_list - убийца move семантики
От: ViTech  
Дата: 23.10.18 10:44
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Здравствуйте, rg45, Вы писали:


R>>Ну можно еше поизвращаться с initializer_list-ом прокси-объектов. Но в этом варианте свои минусы.


V>Да, я в курсе. Казалось бы, базовые операции. Какой-то привкус недодела чувствуется.


Интересно, если в С++20 примут Ranges, то добавят ли в контейнеры конструкторы с Range? Кто-нибудь в курсе?
Пока сам не сделаешь...
Re[3]: initializer_list - убийца move семантики
От: YuriV  
Дата: 23.10.18 11:21
Оценка: +1
Здравствуйте, ViTech, Вы писали:

VT>Работоспособен, но конструкторы копирования/перемещения MovableNonCopyable, похоже, не вызываются,


вызываются, но это действительно не std::initializer_list, а aggregate initialization.
Re[3]: initializer_list - убийца move семантики
От: andyp  
Дата: 23.10.18 11:38
Оценка: +2 :))
Здравствуйте, rg45, Вы писали:

R>Потому, что это aggregate initialization, с ним все в порядке, в отличие от list initialization. Они хоть по форме и похожи, путать не рекоммендуется


Ад с этими всеми инициализациями какой-то. Каждый раз читаю, всё понимаю, через полчаса в голове снова ничего И так раз 800 у меня уже было.
Re[4]: initializer_list - убийца move семантики
От: rg45 СССР  
Дата: 23.10.18 11:43
Оценка: +1
Здравствуйте, andyp, Вы писали:

R>>Потому, что это aggregate initialization, с ним все в порядке, в отличие от list initialization. Они хоть по форме и похожи, путать не рекоммендуется


A>Ад с этими всеми инициализациями какой-то. Каждый раз читаю, всё понимаю, через полчаса в голове снова ничего И так раз 800 у меня уже было.


Так просто ведь все — aggregate initialization — это старая добрая си-шная инициализация массивов и структур. А list initialization — уродец, основанный на initializer_list-ах, придуманный каким-то студентом, для которого шашечки важнее, чем ехать. Это сугубо мое мнение, если что.
--
Отредактировано 23.10.2018 11:46 rg45 . Предыдущая версия .
Re[5]: initializer_list - убийца move семантики
От: andyp  
Дата: 23.10.18 11:48
Оценка: +2 :))
Здравствуйте, rg45, Вы писали:

R>Так просто ведь все — aggregate initialization — это старая добрая си-шная инициализация массивов и структур. А list initialization — уродец, основанный на initializer_list-ах, придуманный каким-то студентом, для которого шашечки важнее, чем ехать.


Это если только на эти две смотреть. А так там еще default, zero, value, direct, constant... Тьма их. В общем, ты толкнул меня на 801 круг
Re[4]: initializer_list - убийца move семантики
От: ViTech  
Дата: 23.10.18 12:51
Оценка:
Здравствуйте, YuriV, Вы писали:

YV>Здравствуйте, ViTech, Вы писали:


VT>>Работоспособен, но конструкторы копирования/перемещения MovableNonCopyable, похоже, не вызываются,


YV>вызываются, ...


Вот тут не вызываются. Или я что-то не так делаю?

Это может зависеть от используемого компилятора, опций компиляции.
Пока сам не сделаешь...
Re[5]: initializer_list - убийца move семантики
От: YuriV  
Дата: 23.10.18 13:08
Оценка:
Здравствуйте, ViTech, Вы писали:

VT>Вот тут не вызываются. Или я что-то не так делаю?

VT>Это может зависеть от используемого компилятора, опций компиляции.

Нет, нет конструкторы копирования не вызываются, это я туплю.
Re[5]: initializer_list - убийца move семантики
От: rg45 СССР  
Дата: 23.10.18 13:23
Оценка:
Здравствуйте, ViTech, Вы писали:

VT>Вот тут не вызываются. Или я что-то не так делаю?


Здесь просиходит тот самый copy/move elision, описанный в разделе 15.8.3. Компилятор, всместо того, чтобы сначала создавать временные объекты, а потом перемещать их, сразу конструирует объекты по месту назначения. И это еще раз демонстрирует существенную неравнозначность между aggregation и list инициализациями.
--
Re[6]: initializer_list - убийца move семантики
От: σ  
Дата: 24.10.18 18:24
Оценка: 1 (1)
R>Здесь просиходит тот самый copy/move elision, описанный в разделе 15.8.3.

В разделе 15.8.3 Камасутры? Какая версия стандарта?

Ладно. Возьмём 2017-й.
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
— in a return statement …
— in a throw-expression …
— when the exception-declaration of an exception handler …

Не подходит.

Мув-констрактор не вызывается из-за параграфа 17.6.1 раздела 11.6. 2017-го стандарта.

> И это еще раз демонстрирует существенную неравнозначность между aggregation и list инициализациями.


В каком смысле неравнозначность?
Re[4]: initializer_list - убийца move семантики
От: B0FEE664  
Дата: 24.10.18 21:33
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Для себя, кроме как создать функцию и там уже сделать emplace_back, и из нее уже вернуть вектор как R-Value, я ничего удобного не нашел


Это так, что ли?:
template<class T>
void mov(std::vector<T>& v)
{
}

template<class V, class T, class... Targs>
void mov(V& v, T&& t, Targs&&... args)
{
  v.emplace_back(std::move(t));
  mov(v, args...);
}


template<class T, class... Targs>
std::vector<T> ini(T&& t1, Targs&&... args)
{
  std::vector<T> v;
  mov(v, t1, args...);
  return v;
}
//...
std::vector<MovableNonCopyable> r{ini(MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable())};
И каждый день — без права на ошибку...
Re[5]: initializer_list - убийца move семантики
От: rg45 СССР  
Дата: 25.10.18 10:25
Оценка: +1
Здравствуйте, B0FEE664, Вы писали:

V>>Для себя, кроме как создать функцию и там уже сделать emplace_back, и из нее уже вернуть вектор как R-Value, я ничего удобного не нашел


BFE>Это так, что ли?:

BFE>
  Скрытый текст
BFE>
BFE>template<class T>
BFE>void mov(std::vector<T>& v)
BFE>{
BFE>}

BFE>template<class V, class T, class... Targs>
BFE>void mov(V& v, T&& t, Targs&&... args)
BFE>{
BFE>  v.emplace_back(std::move(t));
BFE>  mov(v, args...);
BFE>}


BFE>template<class T, class... Targs>
BFE>std::vector<T> ini(T&& t1, Targs&&... args)
BFE>{
BFE>  std::vector<T> v;
BFE>  mov(v, t1, args...);
BFE>  return v;
BFE>}
BFE>//...
BFE>std::vector<MovableNonCopyable> r{ini(MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable())};
BFE>



Зачем же ты move прямо внутрь запихнул? move ведь не всегда должно быть, а только когда нужно:

https://ideone.com/VL1Kci

#include <iostream>
#include <type_traits>
#include <vector>

struct A
{
  A() { std::cout << "Default" << std::endl; }
  A(A&&) noexcept { std::cout << "Move" << std::endl; }
  A(const A&) { std::cout << "Copy" << std::endl; }
};

template <typename T, typename...U>
std::vector<std::decay_t<T>> make_vector(T&& t, U&&...u)
{
  std::vector<std::decay_t<T>> v;
  v.emplace_back(std::forward<T>(t));
  std::initializer_list<int>{(v.emplace_back(std::forward<U>(u)), 0)...};
  return v;
}

int main()
{
   const A a1;
   A a2;
   auto v = make_vector(A(), a1, std::move(a2));
}


Output:
Default
Default
Default
Move
Copy
Move
Move
Move
Move
--
Re: initializer_list - убийца move семантики
От: uzhas Ниоткуда  
Дата: 25.10.18 10:41
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Будьте осмотрительны


желтенько
не смог пройти мимо
Re[6]: initializer_list - убийца move семантики
От: B0FEE664  
Дата: 25.10.18 11:58
Оценка:
Здравствуйте, rg45, Вы писали:

R>Зачем же ты move прямо внутрь запихнул? move ведь не всегда должно быть, а только когда нужно:

Исходил из того, что у MovableNonCopyable не может быть копий. Согласен на std::forward<T>(t).
И каждый день — без права на ошибку...
Re[6]: initializer_list - убийца move семантики
От: N. I.  
Дата: 25.10.18 14:12
Оценка: 11 (2)
rg45:

R>move ведь не всегда должно быть, а только когда нужно:

R>https://ideone.com/VL1Kci

При желании количество перемещений можно ещё сократить:
https://wandbox.org/permlink/5GPsTQXVnTI1hwYT
Re[6]: initializer_list - убийца move семантики
От: B0FEE664  
Дата: 30.10.18 14:57
Оценка: +1
Здравствуйте, andyp, Вы писали:

R>>Так просто ведь все — aggregate initialization — это старая добрая си-шная инициализация массивов и структур. А list initialization — уродец, основанный на initializer_list-ах, придуманный каким-то студентом, для которого шашечки важнее, чем ехать.


A>Это если только на эти две смотреть. А так там еще default, zero, value, direct, constant... Тьма их. В общем, ты толкнул меня на 801 круг


Главное помнить, что:
std::vector<int> v2(1, 2); — это один элемент со значением 2
std::vector<int> v1{1, 2}; — это два элемента со значениями 1 и 2
И каждый день — без права на ошибку...
Re: initializer_list - убийца move семантики
От: Анатолий Широков СССР  
Дата: 30.10.18 15:25
Оценка:
Здравствуйте, rg45, Вы писали:

R>Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями:


#include <iostream>
#include <initializer_list>

struct MovableNonCopyable
{
  MovableNonCopyable() {
    std::cout << "1" << std::endl;
  }
  MovableNonCopyable(MovableNonCopyable&&) {
    std::cout << "2" << std::endl;
  }
  MovableNonCopyable& operator=(MovableNonCopyable&&) = default;
  MovableNonCopyable(MovableNonCopyable const &) = delete;
  MovableNonCopyable& operator=(MovableNonCopyable const &) = delete;
};

struct MovableNonCopyableConsumer {
  MovableNonCopyableConsumer(std::initializer_list<MovableNonCopyable> lst) {
    for(const MovableNonCopyable& i : lst) {
    }
  }
};


int main() {
  MovableNonCopyableConsumer v { MovableNonCopyable(), MovableNonCopyable(), MovableNonCopyable() };
}


вывод:

1
1
1

Re[2]: initializer_list - убийца move семантики
От: B0FEE664  
Дата: 30.10.18 15:54
Оценка:
Здравствуйте, Анатолий Широков, Вы писали:

R>>Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями:


АШ>вывод:

Всё это очень интересно, но жаль, что перемещение элементов из списка вам так и не удалось продемонстрировать.
И каждый день — без права на ошибку...
Re[7]: initializer_list - убийца move семантики
От: andyp  
Дата: 30.10.18 17:52
Оценка: 1 (1) :)
Здравствуйте, B0FEE664, Вы писали:

BFE>Главное помнить, что:

BFE>std::vector<int> v2(1, 2); — это один элемент со значением 2
BFE>std::vector<int> v1{1, 2}; — это два элемента со значениями 1 и 2

Угу, что Карл Маркс и Фридрих Энгельс — два человека, а не четыре, Слава КПСС — вообще не человек, а
auto c = {1,2,3,4};

есть std::initializer_list<int>
Re[3]: initializer_list - убийца move семантики
От: Jack128  
Дата: 16.11.18 07:31
Оценка: +1
Здравствуйте, B0FEE664, Вы писали:

BFE>Здравствуйте, Анатолий Широков, Вы писали:


R>>>Будьте осмотрительны, используя initializer_list для инициализации. Помните, его элементы всегда копируются, а не перемещаются, даже когда вы наполняете его rvalue значениями:


АШ>>вывод:

BFE>Всё это очень интересно, но жаль, что перемещение элементов из списка вам так и не удалось продемонстрировать.

const_cast и вперёд.

template <class T>
std::vector<T> make_vector(std::initializer_list<T> lst) {
    std::vector<T> result;
    result.reserve(lst.size());
    for(const T& item: lst) {
        T& item2 = const_cast<T&>(item);
        result.emplace_back(std::move(item2));
    }
    return result;
}


Вообще проблема не в initializer_list как каком то специальном типе, а в том, что его итераторы возвращают указатели на _константные_ элементы. а их перемещать нельзя.
Re[7]: initializer_list - убийца move семантики
От: Skorodum Россия  
Дата: 16.11.18 08:44
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Главное помнить, что:

BFE>std::vector<int> v2(1, 2); — это один элемент со значением 2
BFE>std::vector<int> v1{1, 2}; — это два элемента со значениями 1 и 2

Недавно наступил на эти грабли... (смешанные чувства)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.