8

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...

2
  • 2
    The problem is not the deleter, but the fact that std::unique_ptr is move-only and the std::initialiser_list constructor copies the elements. I'm afraid you're stuck with calling .push_back. Commented Oct 13, 2017 at 20:00
  • I was thinking of this possibility because initialize a container<unique_ptr> in a header would be easier for me. Otherwise, I'll have to define another function to populate the container. This is ok, I have no issue with push_back(), but I thought there would be a more concise way. Commented Oct 13, 2017 at 20:15

1 Answer 1

20

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;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Just bumped into this, brilliant! Any hints for maps? What I would need is exactly the exercise left to the reader :-D ("which still needs massaging to work right with associative containers as we also want to move the key").
@SergioLosilla In deducinG T, you need to remove the const from the key part of the std::pair<const K, V> that is std::map<K,V>::value_type so move-from works (efficiently)
I am learning a lot here, thanks :-) If you don't mind I will keep asking. With using iptr = std::unique_ptr<int>; The following works vector_from_il<iptr>({iptr{new int{3}}, iptr{new int{5}}}); But the following doesn't, because template deduction fails (I am using g++ 8.1.1 with -std=c++14): container_from_il<std::vector<iptr>>({iptr{new int{6}}, iptr{new int{9}}});
I added a question to follow up on this, so I can pester other people as well :-) stackoverflow.com/questions/50629016/…
I added a follow up question for a related case involving polymorphism, stackoverflow.com/questions/72450903/…

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.