18

Have a look at the following code:

#include <utility>
#include <map>

// non-copyable but movable
struct non_copyable {
    non_copyable() = default;

    non_copyable(non_copyable&&) = default;
    non_copyable& operator=(non_copyable&&) = default;

    // you shall not copy
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

int main() {
    std::map<int, non_copyable> map;
    //map.insert({ 1, non_copyable() });  < FAILS
    map.insert(std::make_pair(1, non_copyable()));
    // ^ same and works
}

Compiling this snippet fails when uncommenting the marked line on g++ 4.7. The error produced indicates that non_copyable can't be copied, but I expected it to be moved.

Why does inserting a std::pair constructed using uniform initialization fail but not one constructed using std::make_pair? Aren't both supposed to produce rvalues which can be successfully moved into the map?

0

2 Answers 2

22

[This is a complete rewrite. My earlier answer had nothing to do with the problem.]

The map has two relevant insert overloads:

  • insert(const value_type& value), and

  • <template typename P> insert(P&& value).

When you use the simple list-initializer map.insert({1, non_copyable()});, all possible overloads are considered. But only the first one (the one taking const value_type&) is found, since the other doesn't make sense (there's no way to magically guess that you meant to create a pair). The first over­load doesn't work of course since your element isn't copyable.

You can make the second overload work by creating the pair explicitly, either with make_pair, as you already described, or by naming the value type explicitly:

typedef std::map<int, non_copyable> map_type;

map_type m;
m.insert(map_type::value_type({1, non_copyable()}));

Now the list-initializer knows to look for map_type::value_type constructors, finds the relevant mova­ble one, and the result is an rvalue pair which binds to the P&&-overload of the insert function.

(Another option is to use emplace() with piecewise_construct and forward_as_tuple, though that would get a lot more verbose.)

I suppose the moral here is that list-initializers look for viable overloads – but they have to know what to look for!

Sign up to request clarification or add additional context in comments.

9 Comments

Yes, that's basically what I wrote before deleting my answer. I have a doubt though: why is an initializer_list<> created here? std::pair<int, non_copyable> does not seem to have a constructor that takes one. I thought the uniform initialization syntax would just pick the regular constructor of pair<>.
Also, initializer_list<> elements must be all of the same type, no?
+1 to both comments. There shouldn't even be an initializer_list in my example. It's more like a call to std::pair's constructor using uniform initialization.
I suppose what's surprising is that map does not have an insert overload for value_type &&, while vector does.
@KerrekSB C++17 added one, BTW
|
0

Besides other answers of providing move (assignment) constructor, you could also store the non-copyable object through pointer, especially unique_ptr. unique_ptr will handle resource movement for you.

1 Comment

This is confusing answer. unique_ptr will not handle resource movement of the object, but rather you are storing only the pointer to the object itself, therefore no resources of the object are moved - only the unique_ptr itself is moved.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.