Search code examples
c++11privatestdmap

C++11 class in std::map as Value with private constructors


Here is the simplified version of the class which is stored as value in a map which works fine in VS2008 (note that all members are private):

class Value{
    friend class FriendClass;
    friend class std::map<std::string, Value>;
    friend struct std::pair<const std::string, Value>;
    friend struct std::pair<std::string, Value>;

    Value() {..}
    Value(Value const& other) {..}

    ... rest members...
};

Code (called from FriendClass, so this can reach private constructors) :

FriendClass::func()
{
    std::map<const std::string, Value> map;
    map.insert(std::make_pair(std::string("x"), Value()));
}

This compiles w/o any error in VS2008, but fails on VS2015/C++11:

file.cpp(178): error C2664: 'std::_Tree_iterator>>> std::_Tree>::insert(std::_Tree_const_iterator>>>,const std::pair &)': cannot convert argument 1 from 'std::pair' to 'std::pair &&'
      with
      [
          _Kty=std::string,
          _Ty=Value,
          _Pr=std::less,
          _Alloc=std::allocator>
      ]
      and
      [
          _Kty=std::string,
          _Ty=Value
      ]
  file.cpp(178): note: Reason: cannot convert from 'std::pair' to 'std::pair'
      with
      [
          _Kty=std::string,
          _Ty=Value
      ]
  file.cpp(178): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

If I make the Value copy constructor public, it compiles fine in VS2015 as well.

But that was private with purpose, and only made available for std::map and std::pair. However, it seems in C++11 additional friend access are also necessary to declare. Which are these?

Thank you.


Solution

  • I don't have access to the compilers you mentioned, but here's what I'm seeing on g++ 5.3.

    Consider the following essentially-same version of your question:

    #include <map>                                                                                                                             
    #include <utility>
    
    
    class foo
    {
        friend std::pair<const int, foo>;
    
        foo(const foo &other){}
    
    public:
        foo(){}
    };
    
    
    int main()
    {
        using map_t = std::map<int, foo>;
    
        map_t m;
        m.insert(std::make_pair(2, foo()));
        // m.emplace(2, foo());
    }
    

    (The default ctor is public, but that's non-essential and just makes the example shorter.)

    In main, note the two lines

    m.insert(std::make_pair(2, foo()));
    // m.emplace(2, foo());
    

    Reversing the comments builds fine, but the version shown doesn't:

    /usr/include/c++/5/bits/stl_pair.h: In instantiation of ‘constexpr    std::pair<_T1, _T2>::pair(_U1&&, const _T2&) [with _U1 = int; <template-parameter-2-2> = void; _T1 = int; _T2 = foo]’:
    /usr/include/c++/5/bits/stl_pair.h:281:72:   required from ‘constexpr std::pair<typename std::__decay_and_strip<_Tp>::__type, typename   std::__decay_and_strip<_T2>::__type> std::make_pair(_T1&&, _T2&&) [with _T1 = int; _T2 = foo; typename std::__decay_and_strip<_T2>::__type = foo; typename std::__decay_and_strip<_Tp>::__type = int]’
    stuff.cpp:21:34:   required from here
    stuff.cpp:9:2: error: ‘foo::foo(const foo&)’ is private
     foo(const foo &other){}
     ^
    

    Looking at the source code std_pair.h shows that indeed it is trying to call the copy constructor. Unfortunately, you friended std::pair, but not std::make_pair.

    The emplace version doesn't have this problem, but I suspect that this is implementation dependent. In general, if you want a container to store a completely opaque class, I would suggest that you use a container of std::shared_ptrs to them. This allows you to completely specify which function/class can create/copy objects in your own code, and makes no assumptions on the library's code.