Вариант использования для std :: forward_as__tuple - PullRequest
3 голосов
/ 09 апреля 2020

Cppreference дает для std::forward_as_tuple следующий пример (см. здесь )

#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);
}

В чем преимущество простого написания

 m.emplace(std::make_pair(20,std::string(20,'a')));

1 Ответ

4 голосов
/ 09 апреля 2020

Это позволяет избежать создания ненужных или потенциально невозможных копий объектов.

Сначала давайте рассмотрим тип значения, отличный от std::string. Я буду использовать что-то, что не может быть скопировано, но это также относится к вещам, которые можно скопировать, но для которых это дорого:

struct NonCopyable
{
    NonCopyable(int a, char b) {} // Dummy
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
};

Как мы можем вставить что в std::map<int, NonCopyable> m? Давайте go через возможности:

m.insert({10, NonCopyable{10, 'a'}});

Этот не будет работать . Он принимает ссылку на std::pair и копирует ее, что требует копирования объекта NonCopyable, что невозможно.

m.emplace(10, NonCopyable{10, 'a'}});

Это также не будет работать . Хотя он создает значение std::pair на месте, он все равно должен скопировать объект NonCopyable.

m.emplace(std::piecewise_construct,
          std::tuple{10},
          std::tuple{10, 'a'});

Наконец, то, что работает . Элемент std::pair создается на месте, как и его два подобъекта.

Но давайте рассмотрим другую ситуацию. Рассмотрим этот класс:

struct UsesNonCopyable
{
    UsesNonCopyable(const NonCopyable&) {}
    UsesNonCopyable(const UsesNonCopyable&) = delete;
    UsesNonCopyable& operator=(const UsesNonCopyable&) = delete;
};

Теперь, как мы можем добавить элементы в std::map<int, UsesNonCopyable> m?

Первые два параметра выше не будут работать по той же причине, что и в предыдущий случай, но внезапно ни третий не будет:

m.emplace(std::piecewise_construct,
          std::tuple{10},
          std::tuple{NonCopyable{10, 'a'}});

Этот не будет работать , потому что NonCopyable объект должен быть скопирован в std::tuple объект, который передается конструктору std::pair.

Здесь std::forward_as_tuple входит:

m.emplace(std::piecewise_construct,
          std::tuple{10},
          std::forward_as_tuple(NonCopyable{10, 'a'}));

Это работает , потому что теперь вместо передачи m.emplace кортеж, содержащий копию объекта NonCopyable, мы используем std::forward_as_tuple для создания кортежа, который содержит ссылку на объект NonCopyable. Эта ссылка передается конструктору std::pair, который, в свою очередь, перенаправляет его в конструктор UsesNonCopyable.


Обратите внимание, что большая часть этого усложнения устранена с помощью C ++ 17 добавление std::map::try_emplace, пока ваш тип ключа копируемый. Следующая будет работать просто отлично и значительно проще:

std::map<int, UsesNonCopyable> m;
m.try_emplace(10, NonCopyable{10, 'a'});
...