Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::map emplace fails with explicit constructor

Tags:

c++

c++11

class A {
public:
    explicit A(int x) {}
};

vector<A> v;
v.push_back(1);  // compiler error since no implicit constructor
v.emplace_back(1); // calls explicit constructor

The above is from a video by David Stone. What I fail to understand is why does emplace_back call the explicit constructor? I do not see anything in the C++ standard that makes this legit. Only after listening to David Stone's youtube video, I found out about this.

Now, I try the same with std::map.

map<int, A> m;
m.insert(pair<int, A>(1, 2)); // compiler error since no implicit constructor
m.emplace(1, 2); // compiler error since no implicit constructor

Why does emplace fail here ? If emplace_back can call explicit constructor, why doesn't emplace do the same ?

like image 480
Amarnath Shanbhag Avatar asked Apr 16 '17 07:04

Amarnath Shanbhag


3 Answers

emplace method inserts elements by explicitly calling constructor with placement new operator. While emplacing into map you need separately forward arguments for constructing key and value.

m.emplace
(
    ::std::piecewise_construct // special to enable forwarding
,   ::std::forward_as_tuple(1) // arguments for key constructor
,   ::std::forward_as_tuple(2) // arguments for value constructor
);
like image 75
user7860670 Avatar answered Oct 06 '22 20:10

user7860670


The emplace functions invoke your constructor as described in the standard at http://eel.is/c++draft/container.requirements.general#15.5

T is EmplaceConstructible into X from args, for zero or more arguments args, means that the following expression is well-formed:

allocator_traits<A>::construct(m, p, args)

This means that it ultimately comes down to your allocator. Looking at the reference for what that calls means, we can check http://en.cppreference.com/w/cpp/memory/allocator_traits/construct

We see that if the allocator does not have a construct member function, or if it is std::allocator, then the call is equivalent to

::new (static_cast<void*>(p)) T(std::forward<Args>(args)...)

For std::map<int, A>, the type T in your expression is std::pair<int const, A> and args... is 1, 2. So to know whether your call to emplace is well-formed, we just need to decide whether a call to std::pair<int const, A>(1, 2) is valid. So for that, we look at the documentation for std::pair: http://en.cppreference.com/w/cpp/utility/pair/pair

The constructor in question is listed as /*EXPLICIT*/ constexpr pair( const T1& x, const T2& y ); (assuming C++17). In other words, it is just like calling a regular function that accepts int const & as the first argument and and A const & as the second. A is only explicitly constructible from int, so your call is ill-formed. The emplace call is only saving you from explicit on any objects you are directly constructing, which in this case is just std::pair, not any arguments to that type.

like image 2
David Stone Avatar answered Oct 06 '22 18:10

David Stone


m.insert(std::pair<int, A>(1, 2)) compiles, I don't know why it doesn't compile for you. Maybe forgot -std=c++11 flag? That's because the std::pair constructor explicitly calls the constructor when it copies the elements into first and second.

If you want to emplace into a std::map, you have to specify a key and a value in a std::pair. You can use std::make_pair for that:

m.emplace(std::make_pair(1, 2));

This compiles, as the pair will get constructed in place in the key/value pair, and the explicit constructor will be called.

like image 1
Rakete1111 Avatar answered Oct 06 '22 18:10

Rakete1111