Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vector<unique_ptr<A> > using initialization list

I have encountered an error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr >' when compile code similar to below using c++ -std=c++14 unique_ptr_vector.cpp -o main

Here is a simplified version:

header file 'my_header.h':

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

class A{
public:
    A() : n(0) {}
    A(int val) : n(val) {} 
    A(const A &rhs): n(rhs.n) {}
    A(A &&rhs) : n(std::move(rhs.n)) {}
    A& operator=(const A &rhs) { n = rhs.n; return *this; }
    A& operator=(A &&rhs) { n = std::move(rhs.n); return *this; }
    ~A() {}

    void print() const { std::cout << "class A: " << n << std::endl; }
private:
    int n;
};

namespace {
    std::vector<std::unique_ptr<A>> vecA = {
        std::make_unique<A>(1),
        std::make_unique<A>(2),
        std::make_unique<A>(3),
        std::make_unique<A>(4)
    };
}

And my src file unique_ptr_vector.cpp:

#include "my_header.h"

using namespace std;

int main()
{
    for(const auto &ptrA : vecA){
        ptrA->print();
    }
    return 0;
}

Do I really need to use push_back(std::make_unique<A>(<some_number>)) individually for each component, Or what would be a preferred way to populate a container in a header? Or is this a bad idea in general?

I have seen problems around like this one, this one, and this one.

I know now Initialization list seems impossible. but what do people normally do with container<unique_ptr>. Should I just simply avoid initialize that in a header...

like image 475
Chen Avatar asked Oct 13 '17 19:10

Chen


1 Answers

Initialization lists are wrappers around const arrays.

unique_ptrs that are const cannot be moved-from.

We can hack around this (in a perfectly legal way) like this:

template<class T>
struct movable_il {
  mutable T t;
  operator T() const&& { return std::move(t); }
  movable_il( T&& in ): t(std::move(in)) {}
};

template<class T, class A=std::allocator<T>>
std::vector<T,A> vector_from_il( std::initializer_list< movable_il<T> > il ) {
  std::vector<T,A> r( std::make_move_iterator(il.begin()), std::make_move_iterator(il.end()) );
  return r;
}

Live example.

Use:

auto v = vector_from_il< std::unique_ptr<int> >({
  std::make_unique<int>(7), 
  std::make_unique<int>(3)
});

If you want to know why initializer lists reference const data, you'll have to track down and read committee minutes or ask someone who was there. I'd guess it is about the principle of least surprise and/or people with bugaboos about mutable data and view types (such as the renaming of array_view to span).

If you want more than just vectors:

template<class C, class T=typename C::value_type>
C container_from_il( std::initializer_list< movable_il<T> > il ) {
  C r( std::make_move_iterator(il.begin()), std::make_move_iterator(il.end()) );
  return r;
}

which still needs massaging to work right with associative containers as we also want to move the key.

template<class VT>
struct fix_vt {
  using type=VT;
};
template<class VT>
using fix_vt_t = typename fix_vt<VT>::type;
template<class VT>
struct fix_vt<const VT>:fix_vt<VT>{};
template<class K, class V>
struct fix_vt<std::pair<K,V>>{
  using type=std::pair<
    typename std::remove_cv<K>::type,
    typename std::remove_cv<V>::type
  >;
};

template<class C, class T=fix_vt_t<typename C::value_type>>
C container_from_il( std::initializer_list< movable_il<T> > il ) {
  C r( std::make_move_iterator(il.begin()), std::make_move_iterator(il.end()) );
  return r;
}
like image 88
Yakk - Adam Nevraumont Avatar answered Oct 20 '22 00:10

Yakk - Adam Nevraumont