6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C++17のstd::applyを精読してみた

Posted at

最近C++に再入門しています。

先日C++14の範囲で「タプルを展開して関数の引数として渡す」関数が必要になったので、C++17で標準ライブラリとして追加された std::apply 関数の実装を読みつつ、ほんの少し改変してC++14でも実行できるように再実装してみました。

この記事は std::apply 関数の説明と内部でどのような処理を行っているか1行1行、自分が理解している範囲で解説する記事になります。

std::apply

std::apply は「タプル型を展開して関数の引数として渡す」という処理を行う関数です1

以下は tuple<int, int, int> を展開し、関数 add(int, int, int) の引数として処理を実行しています。

example
# include <cstdio>
# include <tuple>

void add(int a, int b, int c) {
  printf("total = %d\n", a + b + c);
}

int main() {
  auto tpl = std::make_tuple(1, 2, 3); // make_tupleから {1, 2, 3} を持つtuple<int, int, int>を構築。
  std::apply(add, tpl); /// total = 6
}

標準ライブラリの実装

次に実際の標準ライブラリの実装を読んでみます。実行環境は gcc 9.2.1 です。

std::apply

std::apply は実行する関数とタプルを引数として取り、std::__apply_impl を呼び出します。
以下が std::apply の実装です。

apply
  template <typename _Fn, typename _Tuple>
    constexpr decltype(auto)
    apply(_Fn&& __f, _Tuple&& __t)
    {
      using _Indices
	= make_index_sequence<tuple_size_v<remove_reference_t<_Tuple>>>;
      return std::__apply_impl(std::forward<_Fn>(__f),
			       std::forward<_Tuple>(__t),
			       _Indices{});
    }

まず関数のシグネチャ部分に注目します。

apply
  template <typename _Fn, typename _Tuple>
    constexpr decltype(auto)
    apply(_Fn&& __f, _Tuple&& __t) 
    { /* ... */ }

std::apply は引数を2つ取り、返り値は decltype(auto)2 になっています。
これは std::apply の返り値は _Fn に依存するためであり、正確に返り値の型を推論するのに必要です。

次に実装を見ていきます。

apply
  using _Indices
	= make_index_sequence<tuple_size_v<remove_reference_t<_Tuple>>>;

std::make_index_sequence はテンプレートの引数で要素数を指定すると、テンプレート内に 0...要素数-1 の整数列を構築してくれます。後にこのとき構築した整数列を使用してタプルを展開します。

std::make_index_sequence のテンプレート引数の中身ですが、remove_reference_t で参照を取り外したタプルのサイズを tuple_size_v で取得しています3

tuple_size_vtuple_size<_Tp>::value のエイリアスであり、変数テンプレートが導入されたC++14から使用可能です。

最後に返り値です。

apply
      return std::__apply_impl(std::forward<_Fn>(__f),
                   std::forward<_Tuple>(__t),
                   _Indices{});

std::apply の引数2つと手前で定義した整数列をテンプレートとして持つ _Indices のインスタンスを引数として std::__apply_impl を実行しています。

std::forward は引数の型をそのままに次の関数へと転送します。手前2つの引数の型はユニバーサル参照ですので lvalue でも rvalue の可能性もあります。従って std::forward を使わなければ元の型のまま転送できません。

最後に _Indice{} ですが、ここでは _Indice の持つテンプレート内の整数列が必要です。
そのため、ここではとりあえずインスタンスを作っていますが、__apply_impl では無名の引数になっています。

std::__apply_impl

std::__apply_impl は引数を3つ取り、タプルを展開して std::__invoke に処理を引き継ぎます。

以下実装です。実際の処理は1行しかありません。

__apply_impl
  template <typename _Fn, typename _Tuple, size_t... _Idx>
    constexpr decltype(auto)
    __apply_impl(_Fn&& __f, _Tuple&& __t, index_sequence<_Idx...>)
    {
      return std::__invoke(std::forward<_Fn>(__f),
			   std::get<_Idx>(std::forward<_Tuple>(__t))...);
    }

テンプレート内の size_t... _Idx_Indices 内の整数列が入っています。std::__apply_impl はこの可変長テンプレート引数を使うことでタプルを1つずつ展開していきます。

該当部分はこの関数唯一の処理である、戻り値部分にあります。

__apply_impl
     return std::__invoke(std::forward<_Fn>(__f),
               std::get<_Idx>(std::forward<_Tuple>(__t))...);

std::get<Idx> はテンプレートの引数に当たるタプルの内容を取り出します。_Idx は前述の通り整数ですが、可変長引数のためそのままは使えません。これを展開して一整数として使うには ... でパラメータパックを展開する必要があります。

ここでは、std::get の末尾にくっついていますね。これによってタプルの中身が展開され、std::__invoke の引数として渡されることになりました。

ここまでで apply 関連の実装は終了です(あくまで自分はそう思っています)。
この先は invoke がうまくやってくれます。そして関数が実行されます。

apply (C++14 ver)

std::__apply_impl で関数本体を実行してくれていた invoke は C++17 で追加された機能です4
そのためこの部分だけを置き換えます。apply は実装が同じなので省略しました。

apply14
template <class Fn, class Tuple, size_t... _Idx>
constexpr decltype(auto)
apply_impl(Fn&& f, Tuple&& t, std::index_sequence<_Idx...>) {
	return f(std::get<_Idx>(std::forward<Tuple>(t))...);
}

といっても、関数 f にタプルの中身を展開してそのまま実行しているだけです。
これで疑似applyが完成しました。

(とりあえず動くだけではありますが...)


標準ライブラリ読むのは楽しいですね。 今後もどんどん読める範囲から参考にしていきたい。
  1. 「タプル型を展開して関数の引数として渡す」という説明はこちらの記事から拝借させて頂きました。

  2. decltype(auto) の説明はこの記事が詳しいです。

  3. ここで参照を取り外す意味がわからない。

  4. https://cpprefjp.github.io/reference/functional/invoke.html

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?