Search code examples
c++c++11shared-ptrmultimapemplace

Emplace a pointer to a multimap of shared_ptr's doesn't work


Vector works properly

Header
std::vector<std::shared_ptr<SceneNode>> subnodes_m;

Definition
void CompositeSceneNode::AddChild(SceneNode* subnode_p)
{
    subnodes_m.emplace_back(subnode_p);
}

Multimap doesn't

Header
std::multimap<unsigned int, std::shared_ptr<SceneNode>> subnodes_m;

Definition
void CompositeSceneNode::AddChild(SceneNode* subnode_p, unsigned int layerIndex)
{
    subnodes_m.emplace(layerIndex, subnode_p);
}

I get following error:

error C2664: 'std::pair<_Ty1,_Ty2>::pair(const unsigned int &,const _Ty2 &)' :
cannot convert parameter 2 from 'RendererD3DWrapper::SceneNode *'
to 'const std::shared_ptr<_Ty> &'   

Anybody has a clue?


Solution

  • You can't construct a std::pair<T1,T2> with arguments of types U and V if there is no implicit conversion of U into T1, and V into T2. In your case, there is no implicit conversion of SceneNode* into std::shared_ptr<SceneNode>.

    From the C++ standard:

    § 20.3.2 Class template pair [pairs.pair]

    template<class U, class V> constexpr pair(U&& x, V&& y);
    
    1. Requires: is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true.

    2. Effects: The constructor initializes first with std::forward<U>(x) and second with std::forward<V>(y).

    3. Remarks: If U is not implicitly convertible to first_type or V is not implicitly convertible to second_type this constructor shall not participate in overload resolution.

    Having said that, you can't initialize a std::pair<T1,T2> like below (as emplace builds in-place a std::pair<key_type, mapped_type> known as value_type of std::multimap):

    std::pair<unsigned int, std::shared_ptr<SceneNode>> p( 1, new SceneNode );
    

    because the constructor of std::shared_ptr<T> taking a raw pointer (declared below) is an explicit constructor, hence the error you encounter:

    § 20.9.2.2 Class template shared_ptr [util.smartptr.shared]

    [...]
    
    template<class Y> explicit shared_ptr(Y* p);
    

    In C++11 you should either build a std::shared_ptr<T> before calling emplace:

    subnodes_m.emplace(layerIndex, std::shared_ptr<SceneNode>(subnode_p));
    

    , or you can forward arguments to the constructors of pair's elements (rather than forwarding them to the constructor of std::pair<T1,T2> itself), with a piecewise construction:

    subnodes_m.emplace(std::piecewise_construct
                     , std::forward_as_tuple(layerIndex)
                     , std::forward_as_tuple(subnode_p));
    

    DEMO

    Why does it work with std::vector of std::shared_ptr's then?

    The std::vector<std::shared_ptr<T>>::emplace_back member function forwards the arguments of emplace_back to the constructor of std::shared_ptr<T>, satisfying the explicit context requirement. In case of a map and a multimap, the emplaced type is a pair which has the constructor that forwards arguments further into its elements disabled if the conversion between argument's and parameter's types of those elements is not implicit (as cited above).