Search code examples
c++dictionarycopyshared-ptrcopy-constructor

Uncopyable object, map and shared_ptr : error in the copy constructor


I have an issue constructing my non-copyable object . Let consider the following example :

class Uncopyable{
protected:
    Uncopyable(){};
    ~Uncopyable(){};

private:
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const Uncopyable&);
};


class Base { };
class Derived : public Base { };

class A : private Uncopyable {
public:
    A(std::map<std::string, std::shared_ptr<Base>> & inMap);
private:
    A(const A&);
    A& operator=(const A&);
};

int main() {
    std::map<std::string, std::shared_ptr<Derived>> lMap;
    std::shared_ptr<A> oA(std::make_shared<A>(lMap));
}

If I assume my object A to be non-copyable , it does not work.As a pointer, I expect my object A to understand that Derived is a Base, but instead I am getting the following message:

error C2664: 'A::A(const A &)' : cannot convert argument 1 from 'std::map<std::string,std::shared_ptr<Derived>,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>>' to 'std::map<std::string,std::shared_ptr<Base>,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>> &'
1>          with
1>          [
1>              _Kty=std::string
1>  ,            _Ty=std::shared_ptr<Derived>
1>          ]
1>          and
1>          [
1>              _Kty=std::string
1>  ,            _Ty=std::shared_ptr<Base>
1>          ]

Thanks.


Solution

  • The error has nothing to do with A's noncopyability and indeed is not in the copy constructor - it's in the map constructor. The error has to do with the fact that A' constructor takes a

    std::map<std::string, std::shared_ptr<Base>> &
    

    and you are passing a:

    std::map<std::string, std::shared_ptr<Derived>>
    

    An argument that is an lvalue reference of type T can only be satisfied by an lvalue of type T or of a type derived from T (or of a type with an operator T&). But std::map<std::string, std::shared_ptr<Derived>> does not actually inherit from std::map<std::string, std::shared_ptr<Base>> - nor are the two types related at all - there is no cross-type constructor for std::map.

    In other words, just because a D is a B doesn't mean that a map<X, D> is a map<X, B>. There is no covariance of types in the C++ type system in this way. Some types at least allow you to construct a Class<B> from a Class<D> (e.g. std::shared_ptr), but that's not true of the standard containers (e.g. vector, map, ...)

    You will have to change lMap to hold std::shared_ptr<Base>s in order for this to work. It can internally hold std::shared_ptr<Derived>s - but the map types have to match.


    Side-note, in C++11 you don't need Uncopyable. You can simply explicitly delete those operations:

    A(A const& ) = delete;
    A& operator=(A const& ) = delete;