Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unordered_map of boost::noncopyable can't return references from operator[]

Tags:

c++

boost

To demonstrate my problem, consider this simple program that does not compile:

#include <boost/noncopyable.hpp>
#include <unordered_map>

class foo : boost::noncopyable { };

int main()
{
    std::unordered_map<int, foo> m;
    auto & element = m[0];

    return 0;
}

Using the current version of boost (1.52), Visual Studio 2012 returns the error: cannot access private member declared in class 'boost::noncopyable_::noncopyable.

The operator [] for std::unordered_map returns a reference to the element at the provided key, which at first glance seems like it should work -- I've asked for a reference to the element, not a copy of it.

My understanding of the problem is this (which might be wrong, as I haven't used C++ in a while). If the key is not found, unordered_map creates a new element and returns a reference to the new element. boost::noncopyable defines a (private) copy constructor but not a move constructor, and so a move constructor is not generated by the compiler. In its operator[], std::unordered_map makes use of std::move, but since boost::noncopyable doesn't define a move constructor, it falls back to the copy constructor. Since the copy constructor is private, the compilation fails.

What prompted this post is that I'm trying to create an unordered_map of boost::signal2::signal, which inherits from boost::noncopyable. Short of hacking the boost library, is there a simple workaround I can do? Wrapping the signal in a unique_ptr is an option, but it seems to me I might be doing something wrong here.

Update:

I may have posted too soon! It appears impossible to add a subclass of boost::noncopyable to unordered_map. Insert, operator[], and emplace all use either a copy constructor (which is private), or a move operation (which doesn't exist for boost::noncopyable). To me this seems a major limitation. Is it even possible to create an unordered_map that contains boost::noncopyable objects? I'm explicitly not trying to copy them -- I want them to spend their entire lifespan inside the unordered_map.

like image 838
Sentient Avatar asked Dec 22 '12 08:12

Sentient


2 Answers

It's not impossible to use a subclass of boost::noncopyable in an unordered_map, you simply have to define a move constructor for you type. C++ does not create a default move constructor if you've made your own copy construct (which is what boost::noncopyable does). Also, if it did define default move constructor, it would try to call the parent's copy constructor which is private. So you must define a move constructor that doesn't try to call boost::noncopyable's copy constructor. For example this works fine:

#include <boost/noncopyable.hpp>
#include <unordered_map>

struct foo : public boost::noncopyable
{
    foo() = default;
    foo(foo&&) {}
};

int main()
{
    std::unordered_map<int, foo> m;
    auto & element = m[0];

    return 0;
}
like image 177
David Brown Avatar answered Oct 06 '22 01:10

David Brown


This likely isn't exactly what you're looking for, but I figured I'd toss it out there. The one thing to note is the second value of the returned pair from emplace(), which indicates the second call does not introduce a new member, nor copy over the exiting member.

Again, I don't know if this is closer to what you want, but worth a shot. I likely did something wrong, as I'm not overtly familiar with the C++11 standard library as others. Sorry about that if so.

Finally, please note this is not attempting to address the OP's request of using operator []() for insert+access. Rather, it attempts to simply get a boost::noncopyable derivation constructed into an unordered_map<>. To access you would likely need a combination of the below as well as an initial find() to determine if the tag exists initially.

Anyway...

#include <boost/noncopyable.hpp>
#include <iostream>
#include <unordered_map>

class Foo : public boost::noncopyable
{
public:
    Foo(int value) : value(value) {};

    void setValue(int value) { this->value = value; }
    int getValue() const { return value; }

private:
    int value;
};


int main(int argc, char *argv[])
{
    typedef std::unordered_map<std::string, Foo> MyMap;
    MyMap mymap;

    // throw ("test".1) into the map
    auto p = mymap.emplace("test", 1);
    auto q = mymap.emplace("test", 2); // should not overwrite the first.

    // dump content
    cout << p.first->second.getValue() << '(' << p.second << ')' << ' '
         << q.first->second.getValue() << '(' << q.second << ')' << endl;

    // modify through the second returned iterator/bool pair.
    q.first->second.setValue(3);

    // dump again, see if p was also updated.
    cout << p.first->second.getValue() << '(' << p.second << ')' << ' '
         << q.first->second.getValue() << '(' << q.second << ')' << endl;

    return 0;
}

Output

1(1) 1(0)
3(1) 3(0)
like image 44
WhozCraig Avatar answered Oct 06 '22 01:10

WhozCraig