LINUX.ORG.RU

Ниасилил C++ vector<unique_ptr<T>> list initialization

 ,


0

5

Допустим есть у нас следующее:

#include <memory>
#include <vector>
#include <iostream>
using namespace std;

class Base {
        int i;
    public:
        Base(int ai) : i(ai){}
        virtual ~Base() {}
        virtual void m() {
            cout << "Base::m() " << i << endl;
        }
};
class A : public Base {
    public:
        A() : Base(1) {}
        virtual ~A() {}
};
class B : public Base {
        char c;
    public:
        B(char ac) : Base(2), c(ac) {}
        virtual ~B() {}
        virtual void m() {
            Base::m();
            cout << "B::m() " << c << endl;
        }
};

int main(){
    vector<unique_ptr<Base>> v{
        /* ??? */
    };
    for (auto& a : v)
        a->m();
}

Что нужно написать, что бы инициализировать v значениями new Base(10), new A(), new B('x')?

make_unique<Base>(), unqiue_ptr<Base>(new ??()) не работают.

Проблема в том, что у std::initializer_list нет move-конструктора.

У него есть move-конструктор. Но std:begin и std::end для него возвращают константный указатель, а данные по константному указателю нельзя move-ать.

auto l = {1, 2, 3};
int&& num = std::move(*l.begin()); // error

Вместо него компилятор пытается использовать copy-конструктор, а его нет у std::unique_ptr

auto l = {
    std::unique_ptr<Base> {new Base {10}},
    std::unique_ptr<Base> {new A {}},
    std::unique_ptr<Base> {new B {'x'}},
};
std::vector<unique_ptr<Base>> v {std::move(l)}; // error

Не могу придумать ничего лучше этого:

std::vector<unique_ptr<Base>> v; 
v.push_back(std::unique_ptr<Base> {new Base {10}});
v.push_back(std::unique_ptr<Base> {new A {}});
v.push_back(std::unique_ptr<Base> {new B {10}});

Если сильно хочется сделать вектор константным, то можно так:

const std::vector<unique_ptr<Base>> v = [] () {
    std::vector<unique_ptr<Base>> result;
    result.push_back(std::unique_ptr<Base> {new Base {10}});
    result.push_back(std::unique_ptr<Base> {new A {}});
    result.push_back(std::unique_ptr<Base> {new B {10}});
    return result;
} ();

Ничего не поделаешь, это std::initializer_list.

Kuzy ★★★ ()
Последнее исправление: Kuzy (всего исправлений: 1)

Уже было:

https://www.linux.org.ru/forum/development/11067850

В двух словах - через initializer_list сделать не получится, проще всего через серию push_back.

v.push_back(make_unique<Base>(10));
v.push_back(make_unique<A>());
v.push_back(make_unique<B>('x'));
archie ()

Так это же руки выкручивающий на ровном месте цепепе :-) Кто-нибудь прокомментирует такой вот кодик:

#include <iostream>
#include <memory>
#include <vector>

template<typename T>
decltype(auto) vector_from(std::initializer_list<T> il)
{
  std::vector<T> result;
  for (auto& e : il)
    result.push_back(std::move(const_cast<T&>(e)));
  return result;
}

int main(int argc, char *argv[])
{
  using namespace std;
  auto v = vector_from({make_unique<int>(10), make_unique<int>(20), make_unique<int>(30)});
  for (auto& e : v)
    cout << *e << endl;
  return 0;
}
Кто-нибудь может сказать, что в нём плохого? :-)

anonymous ()
Ответ на: комментарий от anonymous

Кто-нибудь может сказать, что в нём плохого?

Если решать задачу ТС через функцию, то тут вообще не нужен initializer_list, плюс лучше сразу делать более специализированную функцию:

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

template<class T, class... Args>
auto vector_uptr( Args&&... args )
{
    vector<unique_ptr<T>> result;
    for( auto& e : { args... } )
        result.emplace_back( new T( e ) );
    return result;
}

int main()
{
    for( auto& e : vector_uptr<int>( 10, 20, 30 ) )
        cout << *e << endl;
}
anonymous ()
Ответ на: комментарий от anonymous

Хотя это получилась не задача ТС, для нее таки лучше оставить make_unique в аргументах функции.

anonymous ()
Ответ на: комментарий от anonymous

Если решать задачу ТС через функцию, то тут вообще не нужен initializer_list, плюс лучше сразу делать более специализированную функцию:

Да ну на хрен писать для каждого случая специализированную функцию :-) Зачем тогда цепепе с его шаблонами? :-)

anonymous ()
Ответ на: комментарий от anonymous

Зачем тогда цепепе с его шаблонами?

Не только с шаблонами, но и со статической типизацией. Попробуй в том же Rust решить задачу ТС с Vec, Box и трейтами. В лучшем случае выйдет тот самый .push.

anonymous ()
Ответ на: комментарий от anonymous

В лучшем случае выйдет тот самый .push.

Не понял :-)

anonymous ()
Ответ на: комментарий от anonymous

Не понял

Заполнение вектора одиночными вызовами .push, без возможности инициализировать вектор начальными значениями.

anonymous ()
Ответ на: комментарий от anonymous

Заполнение вектора одиночными вызовами .push, без возможности инициализировать вектор начальными значениями.

Не, я не за это :-) Идея в приведённом мной кодишке в создании ещё одного конструктора std::vector<>, поэтому он параметризован std::initializer_list<T> :-) Семантика ясна и соответствует, хотя и ущербной, но всё же семантике std::vector<> - для вызова конструктора с std::initializer_list<> используй фигурные скобочки :-) А у тебя уже семантика иная - ты используешь конструкцию в круглых скобочках, что соответствует семантике конструктора вектора для создания вектора размера N из элементов T :-)

anonymous ()
Ответ на: комментарий от anonymous

Ну и если быть немного задротом, можно добавить reserve():

template<typename T>
decltype(auto) vector_from(std::initializer_list<T> il)
{
  std::vector<T> result;
  result.reserve(il.size);
  for (auto& e : il)
    result.push_back(std::move(const_cast<T&>(e)));
  return result;
}
Мне бросается в глаза const_cast :-) Но в цепепе в глаза бросается всё, ибо вырвиглазный он :-) Лол :-)

anonymous ()
Ответ на: комментарий от anonymous
result.push_back(std::move(const_cast<T&>(e)))

Это UB.

8.5.4/5
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Константный объект изменяешь.

Я вообще ничего не изменяю :-) Я снимаю константность и перемещаю объект из временного массива в вектор :-) Где тут UB? :-)

anonymous ()
Ответ на: комментарий от anonymous

Я вообще ничего не изменяю :-)

Вернее, я изменяю представление unique_ptr, которое уже в динамической памяти на момент перемещения :-)

anonymous ()
Ответ на: комментарий от anonymous

Хотя, конечно, меня этот код настораживает :-) Потому то я и попросил его прокомментировать :-)

anonymous ()
Ответ на: комментарий от anonymous

Я вообще ничего не изменяю :-)

После ты перемещаешь unique_ptr в вектор и это меняет исходный объект.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

После ты перемещаешь unique_ptr в вектор и это меняет исходный объект.

Что такое исходный объект? :-) initializer_list<>? :-) Он не изменяется :-) Изменяется *представление* unique_ptr<> в initializer_list<> - оно там обнуляется и перемещается в unique_ptr<> в vector<> :-) Где UB? :-)

anonymous ()
Ответ на: комментарий от anonymous

Он не изменяется :-) Изменяется *представление* unique_ptr<> в initializer_list<> - оно там обнуляется и перемещается в unique_ptr<> в vector<> :-)

Мде. И в чем же разница между объектом и его представлением?

tailgunner ★★★★★ ()
Ответ на: комментарий от anonymous

У него move-конструктор как-то так выглядит:

unique_ptr(uniquie_ptr&& that) 
  : pointer {that.pointer} {
  that.pointer = nullptr; // вот тут ты его изменяешь
}

Kuzy ★★★ ()
Ответ на: комментарий от tailgunner

Мде. И в чем же разница между объектом и его представлением?

В их состоянии :-) Это 2 разных объекта с разными инвариантами :-) Состояние объекта, *содержащего* представление, определяется тем, какое именно представление он содержит :-) А представление - это объект со своим состоянием и со своими инвариантами :-) Так вот я состояние представления не изменял :-) А инварианты не нарушил ни в объекте-обёртке, ни в объекте-представлении :-)

anonymous ()
Ответ на: комментарий от Kuzy

Я знаю это :-) Читай ответ для tailgunner :-)

anonymous ()
Ответ на: комментарий от Kuzy

И ещё :-) Тебя ни на что не наводит мысль, что обнуляется *указатель*? :-) Т.е. данные находятся в динамической памяти? :-) Так какое м.б. UB тогда? :-)

anonymous ()
Ответ на: комментарий от anonymous

8.5.4/5
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array.

const int a[5] {1, 2, 3, 4, 5};

const_cast<int&>(a[4]) = 3;

Тоже не UB?

Kuzy ★★★ ()
Ответ на: комментарий от anonymous

Указатель в какой памяти находится? Ты указатель обнуляешь, а не данные.

Kuzy ★★★ ()
Последнее исправление: Kuzy (всего исправлений: 1)
Ответ на: комментарий от Kuzy

Тоже не UB?

Тут другое, см. ответ ниже :-)

anonymous ()
Ответ на: комментарий от Kuzy

Указатель в какой памяти находится?

В том случае указатель, что в unique_ptr<> выделен на стеке и не в read-only памяти :-)

Ты указатель обнуляешь, а не данные.

Константным является сам unique_ptr<>, а не его член pointer :-)

anonymous ()
Ответ на: комментарий от anonymous

Не, я не против, если кто-то реально докажет, что это UB :-) Мне самому интересно, а штудировать стандарт не охота на эту тему :-)

anonymous ()
Ответ на: комментарий от anonymous

В том случае указатель, что в unique_ptr<> выделен на стеке и не в read-only памяти :-)

А сам unique_ptr /целиком/ где?

Константным является сам unique_ptr<>, а не его член pointer :-)

pointer, наверное, mutable, да?

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

А сам unique_ptr /целиком/ где?

Какое мне дело? :-) Я его /целиком/ не изменяю, биты в той памяти не трогаю :-) А вот ты в своём примере как раз это и делаешь, и это UB :-)

pointer, наверное, mutable, да?

Нет, pointer просто не const :-)

anonymous ()
Ответ на: комментарий от anonymous
template <typename T>
void vector_push_back_variadic(std::vector<T>&) {}

template <typename T, typename Arg, typename... Args>
void vector_push_back_variadic(std::vector<T>& vec, Arg&& arg, Args&&... args) {
	vec.push_back(std::forward<T>(arg));
	vector_push_back_variadic(vec, std::forward<Args>(args)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
	const auto size = sizeof...(args);
	std::vector<T> result;
	result.reserve(size);
	
	vector_push_back_variadic(result, std::forward<Args>(args)...);
	
	return result;
}
Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Как тебе угодно :-) Только раз уж ты forward<> используешь, то тогда уже emplace_back() используй :-) Но семантика make_vector<> не соответствует семантике std::vector<> :-) Так что это лажа, без обид :-)

anonymous ()
Ответ на: комментарий от anonymous

Какое мне дело? :-) Я его /целиком/ не изменяю, биты в той памяти не трогаю :-) А вот ты в своём примере как раз это и делаешь, и это UB :-)

Тут тоже биты не трогаешь?

struct A {
	int foo = 0;
};

int main() {
	const A a;
	const_cast<int&>(a.foo) = 3;
	
	return 0;
}

Нет, pointer просто не const :-)

int, просто, не const, не UB.

Kuzy ★★★ ()
Ответ на: комментарий от anonymous

Только раз уж ты forward<> используешь, то тогда уже emplace_back() используй

yasno

Но семантика make_vector<> не соответствует семантике std::vector<> :-) Так что это лажа, без обид :-)

к

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Тут тоже биты не трогаешь?

Я не трогаю :-) И ты не трогаешь биты самого A :-) Ты трогаешь биты A::foo, но не самого A :-) Разницу улавливаешь? :-) И ты забыл конструктор по умолчанию :-) И a.foo тут имеет тип int, а не const int, несмотря на то, что ты создал константный A :-) Т.е. ты const_cast ни к тому применяешь :-) Т.е. никакого UB в таком коде нет,:

struct A {
  A() {};
  int foo;
};

int main() {
  const A a;
  const_cast<A&>(a).foo = 3;

  return 0;
}

anonymous ()
Ответ на: комментарий от anonymous

И ты забыл конструктор по умолчанию

забыл
по умолчанию

Ле.

И ты не трогаешь биты самого A :-) Ты трогаешь биты A::foo, но не самого A :-)

Ну проверь, что там за биты:

struct A {
	int foo = 0;
};

int main() {
	const A a;
	
	std::cout << &a << " " << sizeof(a) << std::endl;
	std::cout << &a.foo << " " << sizeof(a.foo) << std::endl;

	return 0;
}

Лень тебе отвечать дальше, сорь.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Ну проверь, что там за биты:

Лол :-) Юные цепепешники не знают таких элементарных вещей :-) Смотри сюда и удивляйся (а лучше выучи C):

#include <iostream>

struct A {
  int foo{0};
  int bar{0}; // oops
};

int main() {
    const A a;

    std::cout << &a << " " << sizeof(a) << std::endl;
    //    std::cout << &a.foo << " " << sizeof(a.foo) << std::endl;
    std::cout << &a.bar << " " << sizeof(a.bar) << std::endl;

    return 0;
}
Лол :-)

anonymous ()
Ответ на: комментарий от anonymous

// std::cout <<

тоже емакс комментит с отступом? ;)

anonymous ()
Ответ на: комментарий от anonymous

Я не знаю как так вышло :-) Вообще-то нет :-)

anonymous ()
Ответ на: комментарий от Kuzy

То, что структура и её члены - суть разные объекты :-) И константность структуры не делает константными её члены :-) Ну и вообще, если вернуться к оригинальному вопросу, то шаблон initializer_list выглядит так:

template<class E> class initializer_list {
public:
  typedef E value_type;
  // ...
  typedef const E* iterator;
  typedef const E* const_iterator;
  // ...
}
Как видишь, value_type имеет тип E, а не const E :-) Так что const_cast<E*> над const E* ничего плохого не делает :-) По идее :-)

anonymous ()
Ответ на: комментарий от anonymous

И константность структуры не делает константными её члены :-)

Делает, если они не объявлены mutable.

tailgunner ★★★★★ ()
Ответ на: комментарий от anonymous

Как видишь, value_type имеет тип E, а не const E :-) Так что const_cast<E*> над const E* ничего плохого не делает :-) По идее :-)

Я 2 (два) раза приводил эту цитату.

8.5.4/5
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array.

И константность структуры не делает константными её члены :-)

nuff said

Kuzy ★★★ ()
Ответ на: комментарий от tailgunner

Делает, если они не объявлены mutable.

Нет, не делает, если они не объявлены как const :-) Скомпиль:

template<typename T>
class Type_printer;

struct A {
  int foo{0};
};

int main() {
  const A a;
  Type_printer<decltype(a.foo)> TTTT;

  return 0;
}

Потом скомпиль:

template<typename T>
class Type_printer;

struct B {
  const int foo{0};
};

int main() {
  const B b;
  Type_printer<decltype(b.foo)> TTTT;

  return 0;
}

А потом удивляйся :-)

anonymous ()
Ответ на: комментарий от Kuzy

Я 2 (два) раза приводил эту цитату.

Извини, но это не говорит ни о каком UB :-) Nuff said :-)

anonymous ()
Ответ на: комментарий от anonymous

Смотри сюда, недоразумение:

$ cat a.cpp 
struct A {
  int foo;
};

int main() {
  const A a = {0};

  a.foo = 1;
  return 0;
}

$ c++ a.cpp 
a.cpp: In function ‘int main()’:
a.cpp:8: error: assignment of data-member ‘A::foo’ in read-only structure
tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

С тобой всё ясно :-)

~ $ g++ -c -std=c++14 test2a.cpp
test2a.cpp: In function ‘int main()’:
test2a.cpp:10:33: error: aggregate ‘Type_printer<int> TTTT’ has incomplete type and cannot be defined
   Type_printer<decltype(a.foo)> TTTT;
                                 ^
~ $ g++ -c -std=c++14 test2b.cpp
test2b.cpp: In function ‘int main()’:
test2b.cpp:10:33: error: aggregate ‘Type_printer<const int> TTTT’ has incomplete type and cannot be defined
   Type_printer<decltype(b.foo)> TTTT;
                                 ^
Ещё раз убедился, что C++ никто толком не знает :-) Гг :-)

anonymous ()
Ответ на: комментарий от tailgunner

> Веско.

Вот поэтому C лучше, чем C++ :-) Там хотя бы очевидно то, что написано, потому что правила языка намного проще, тайных мест намного меньше, язык намного прямолинейнее :-) В C++17 будет ещё веселее, готовьтесь :-) Только вот долго ли будет существовать C++ после таких переусложнений - вот вопрос :-)

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.