Std::Map Emplace Without Copying Value

std::map emplace without copying value

The arguments you pass to map::emplace get forwarded to the constructor of map::value_type, which is pair<const Key, Value>. So you can use the piecewise construction constructor of std::pair to avoid intermediate copies and moves.

std::map<int, Foo> m;

m.emplace(std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(2.3, "hello"));

Live demo

Is it possible to do a no-copy emplace into map while using aggregate initialization?

You may exploit casts to indirect your construction

template<typename T>
struct tag { using type = T; };

template<typename F>
struct initializer
{
F f;
template<typename T>
operator T() &&
{
return std::forward<F>(f)(tag<T>{});
}
};

template<typename F>
initializer(F&&) -> initializer<F>;

template<typename... Args>
auto initpack(Args&&... args)
{
return initializer{[&](auto t) {
using Ret = typename decltype(t)::type;
return Ret{std::forward<Args>(args)...};
}};
}

And use it as

struct Foo
{
const int& intref_;
std::mutex mutex_;
};

void foo()
{
int i = 42;
std::map<int, Foo> m;
m.emplace(std::piecewise_construct,
std::forward_as_tuple(0),
std::forward_as_tuple(initpack(i)));
}

Note you can't prolong a temporary's lifetime by binding it to a non-stack reference.

How can I try_emplace a POD struct in a std::map?

Your first two attempts

dict.try_emplace(1, 2);
dict.try_emplace(1, 1, 2);

fail because you cannot use try_emplace, emplace etc. to initialize an aggregate from a list of values. Standard allocators construct the types in-place using () instead of {}, which of course will fail for aggregates. See the answers to this question.

The third attempt

dict.try_emplace(1, {1, 2});

fails because a braced-init-list is not an expression, and doesn't have a type, so template argument deduction fails to deduce it as various.

You can get try_emplace to work by specifying you're constructing a various instance

dict.try_emplace(1, various{1, 2});

Or add appropriate constructor(s) to various, which will allow the first two attempts to work.

However, given that you're working with a map containing builtin types, the easiest solution is to just use insert instead of emplacement.

dict.insert({1, {1, 2}});

The map::insert overload called above is std::pair<iterator,bool> insert(value_type&&), where value_type is pair<const int, various>. The nested braced-init-lists are deduced as value_type by the overload resolution rules in over.match.list, specifically the second bullet

If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

The pair(T1 const&, T2 const&) constructor is selected.

How best to use emplace with std::map


Should you just pass the arguments of the constructor

Yes, because this is literally what all emplace() functions are designed for. With insert(), you have to construct an object, and then [usually] copy it into your container. And generally, if you're using a container, you're only constructing so you can put them into the container. As you can see in your tests, it's a bit of extra work.

emplace() was designed to allow you to construct directly into the container. And you do so by providing constructor parameters to the emplace function. insert() is used if you've already got an object and want to put it in a container.

I had a snarky comment that others have noted is worth explaining a bit more. If your class (which I'll call Foo) has single parameter constructors, it may appear that you can do the same thing as emplace() by just passing the single parameter to something like insert() or push_back() or any place that would take a Foo as a parameter. This is a 'feature' of the language where the compiler will implicitly construct a Foo for you and use it. The problem is that under the hood, it's not doing the same thing. Where emplace() will build your object directly in the container, faking it by taking advantage of a single parameter constructor still causes copies to be made. Another downside to consider is this implicit conversion. It can hurt readability of your code or worse, break things. This can be avoided by marking the constructor as explicit.

How to create new entry in std::map without copying the entry value - no pointers

Use emplace:

#include <map>
#include <string>
#include <tuple>

std::map<std::string, X> m;

m.emplace(std::piecewise_construct,
std::forward_as_tuple("nocopy"),
std::forward_as_tuple());

This generalizes to arbitrary consructor arguments for the new key value and mapped value, which you just stick into the respective forward_as_tuple calls.

In C++17 this is a bit easier:

m.try_emplace("nocopy"  /* mapped-value args here */);

Why does std::map emplace need a copy constructor on gcc?

This is not valid in C++11 as published.

The C++11 standard depicts two pair constructors with two parameters:

pair(const T1& x, const T2& y);
template<class U, class V> pair(U&& x, V&& y);

If the first gets chosen, you are doomed for obvious reasons.

The second overload does not participate in overload resolution if, as relevant here, "V is not implicitly convertible to second_type". Here, V is int, second_type is CMyClass. Is int implicitly convertible to CMyClass? No, because the declaration CMyClass x = some_int; is not well-formed, as in C++11 that declaration notionally constructs a CMyClass temporary from some_int and then moves it into x, but CMyClass cannot be moved.

The extra overloads GCC added - as is allowed - have similar constraints.


C++17 substantially revamped the constraints on pair's constructors, which has the side effect of allowing your code to work. Implementers treated this as a retroactive defect fix, which is why GCC 6 accepts your code.



Related Topics



Leave a reply



Submit