std::piecewise_constructの使い方(std::pair)

プログラミングC/C++

c++の std::pair には std::piecewise_construct を渡して要素を直接構築するためのコンストラクタが存在する。このページではこの std::piecewise_construct の使い方について簡単にまとめる。

使い方

std::piecewise_construct は空の構造体 std::piecewise_construct_t 型の変数である。

namespace std {
  struct piecewise_construct_t {};
  constexpr piecewise_construct_t piecewise_construct {};
}

この変数自体は何の効果はない。std::pair のコンストラクタでタグとして使うのが基本的な使い方である。

namespace std {
  template <typename T1, typename T2>
  template <typename... Args1, typename... Args2>
  pair<T1, T2>::pair(piecewise_construct_t, tuple<Args1...> first_args, tuple<Args2...> second_args);

  // pair<T1, T2>(piecewise_construct, std::make_tuple(a1, a2, a3), std::make_tuple(b1))
  // => {.first = T1{a1, a2, a3}, .second = T2{b1}} のイメージ
}

第一要素を first_args から、第二要素を second_args からそれぞれ直接構築したpairをコンストラクトする1。それぞれ事前に作っておいてmoveコンストラクタで渡せばいいと思われるかもしれないが、moveできないクラスやmoveのオーバーヘッドが大きいクラスをpairの要素にしたい場合に有用である2

使用例:

#include <iostream>
#include <utility>
#include <tuple>
#include <string>
#include <vector>

int main() {
    std::pair<std::string, std::vector<int>> p {
        std::piecewise_construct,
        std::make_tuple("AAA"),
        std::make_tuple(4, 0)
    };

    std::cout << p.first << " " << p.second.size() << std::endl;
    // => AAA 4
}

実用上、pairへ要素を直接構築したくなることがあるのかと疑問に思われるかもしれない。しかし、piecewise_constructは std::mapstd::unordered_mapstd::multimap なども同様)への要素挿入時に便利に使えるのである。

#include <iostream>
#include <utility>
#include <tuple>
#include <string>
#include <vector>
#include <map>

int main() {
    std::map<std::string, std::vector<int>> m;

    m.emplace(
        std::piecewise_construct,
        std::make_tuple("AAA"),
        std::make_tuple(4, 0)
    );

    for (auto&& p : m) {
        std::cout << p.first << " " << p.second.size() << std::endl;
    }
    // => AAA 4
}

map::emplace は木の要素を直接構築する関数である。受け取った引数を内部では std::pair<Key, Value> へ横流しするので、std::piecewise_construct を活用してmapの要素を直接構築できるのである。

なお、std::piecewise_constructstd::pair 限定の機能で std::tuple では使えない。map要素を直接構築するために導入された機能なので、tupleには不要だと考えられて導入が見送られたのだろう。

notes

  1. 本筋には全く関係ないが、tupleを展開してコンストラクタを起動するには工夫が必要である。

    template <typename... Args, size_t... Indices>
    T ConstructImpl(std::tuple<Args...> t, std::index_sequence<Indices...>) {
      return T(std::get<Indices>(t)...);
    }
    
    template <typename... Args>
    T Construct(std::tuple<Args...> t) {
      return ConstructImpl(t, std::index_sequence_for<Args...>());
    }

    上記のコードのように、std::index_sequence_for で0, …, n-1のインデックスを作り、std::getで一つずつ取り出せばよい。

  2. メンバ変数で std::array で巨大配列を持っているクラスが一例。moveは可能だが配列全体をコピーする必要がある。

プログラミングC/C++

Posted by komori