Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use case for std::forward_as__tuple

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')));
like image 553
Jan Hackenberg Avatar asked Apr 09 '20 06:04

Jan Hackenberg


1 Answers

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'});
like image 183
Miles Budnek Avatar answered Nov 12 '22 07:11

Miles Budnek