Cppreference gives for std::forward_as_tuple
the following example (see here)
#include <iostream>
#include <map>
#include <tuple>
#include <string>
int main()
{
std::map<int, std::string> m;
m.emplace(std::piecewise_construct,
std::forward_as_tuple(10),
std::forward_as_tuple(20, 'a'));
std::cout << "m[10] = " << m[10] << '\n';
// The following is an error: it produces a
// std::tuple<int&&, char&&> holding two dangling references.
//
// auto t = std::forward_as_tuple(20, 'a');
// m.emplace(std::piecewise_construct, std::forward_as_tuple(10), t);
}
What is the benefit over simply writing
m.emplace(std::make_pair(20,std::string(20,'a')));
It avoids making unnecessary or potentially impossible copies of objects.
First, let's consider a value type other than std::string
. I'll use something that can't be copied, but this also applies to things that can be copied but for which it's expensive to do so:
struct NonCopyable
{
NonCopyable(int a, char b) {} // Dummy
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
How can we insert that into a std::map<int, NonCopyable> m
? Let's go through the possibilities:
m.insert({10, NonCopyable{10, 'a'}});
This won't work. It accepts a reference to a std::pair
and copies it, which requires copying the NonCopyable
object, which is impossible.
m.emplace(10, NonCopyable{10, 'a'}});
This also won't work. While it constructs the std::pair
value in place, it still has to copy the NonCopyable
object.
m.emplace(std::piecewise_construct,
std::tuple{10},
std::tuple{10, 'a'});
Finally, something that works. The std::pair
element is constructed in-place as are its two sub-objects.
But lets consider another situation. Consider this class:
struct UsesNonCopyable
{
UsesNonCopyable(const NonCopyable&) {}
UsesNonCopyable(const UsesNonCopyable&) = delete;
UsesNonCopyable& operator=(const UsesNonCopyable&) = delete;
};
Now how can we add elements to a std::map<int, UsesNonCopyable> m
?
The first two options above won't work for the same reason they didn't in the previous case, but suddenly neither will the third:
m.emplace(std::piecewise_construct,
std::tuple{10},
std::tuple{NonCopyable{10, 'a'}});
This won't work because the NonCopyable
object has to be copied into the std::tuple
object that's passed to std::pair
's constructor.
This is where std::forward_as_tuple
comes in:
m.emplace(std::piecewise_construct,
std::tuple{10},
std::forward_as_tuple(NonCopyable{10, 'a'}));
This works, because now rather than passing m.emplace
a tuple containing a copy of the NonCopyable
object, we use std::forward_as_tuple
to construct a tuple that holds a reference to the NonCopyable
object. That reference gets forwarded on to std::pair
's constructor, which will in turn forward it on to UsesNonCopyable
's constructor.
Note that much of this complication is removed with C++17's addition of std::map::try_emplace
as long as your key type is copyable. The following will work just fine and is significantly simpler:
std::map<int, UsesNonCopyable> m;
m.try_emplace(10, NonCopyable{10, 'a'});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With