简体   繁体   中英

Why does std::insert require CopyConstructibility?

Why doesn't this code work? There is nothing about CopyConstructibility in the docs for the insert method of std::map: http://en.cppreference.com/w/cpp/container/map/insert

I can construct std::pair with the second element being my non_copyable class, but I cannot insert it into map, even though the very first overload of the insert method takes a const value_type&, so this should work fine.

Can someone help me understand why std::map requires this CopyConstructibility?

#include <utility>
#include <map>

struct non_copyable {
    non_copyable(){}

private:
    non_copyable(const non_copyable&);
};

int main() {
    std::pair<int, non_copyable> p; // this works!

    std::map<int, non_copyable> m;
    m.insert(std::pair<int, non_copyable>()); // this does NOT work
}

EXAMPLE: https://wandbox.org/permlink/r3H6CqehfqnHPXDL

... even though the very first overload of the insert method takes a const value_type& , so this should work fine.

No, actually in this case, the correct overload resolution candidate for:

m.insert(std::pair<int, non_copyable>());

is this overloaded member function:

template< class P >
std::pair<iterator,bool> insert( P&& value );

Now this page describes why this doesn't work:

... only participates in overload resolution if std::is_constructible<value_type, P&&>::value == true .

Your non_copyable is not constructible:

using M = std::map<int, non_copyable>;
using P = std::pair<int, non_copyable>;
std::is_constructible<M::value_type, P&&>::value; // false

because there's no available constructor including a default, implicit move constructor generated by the compiler.

The reason non_copyable even disabled its default, implicit move constructor is because of having a user-defined copy constructor, as documented :

If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:

there are no user-declared copy constructors

there are no user-declared copy assignment operators

there are no user-declared move assignment operators

there are no user-declared destructors

Now giving non_copyable a move constructor becomes your responsbility:

struct non_copyable {
    non_copyable(){}
    non_copyable(non_copyable &&) = default;

private:
    non_copyable(const non_copyable&);
};

You may be interested why you have to make any copies at all hence provide copy constructible types even if you could construct a pair directly (emplace it) inside of a map.

It is because to provide the required performance the internal representation of the std::map is usually implemented as a balanced binary search tree (eventually as another data structure with similar performance characteristics) requiring some specific order of stored elements. So after adding or deleting some elements the order may be broken and to preserve its correctness occasional internal moves of the stored elements will be needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM