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

最近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)` の引数として処理を実行しています。

```cpp: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` の実装です。

```c++: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{});
    }
```

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

```cpp:apply
  template <typename _Fn, typename _Tuple>
    constexpr decltype(auto)
    apply(_Fn&& __f, _Tuple&& __t) 
    { /* ... */ }
```
`std::apply` は引数を2つ取り、返り値は `decltype(auto)`[^2] になっています。
これは `std::apply` の返り値は `_Fn` に依存するためであり、正確に返り値の型を推論するのに必要です。

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

```cpp: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_v` は `tuple_size<_Tp>::value` のエイリアスであり、変数テンプレートが導入されたC++14から使用可能です。

最後に返り値です。

```cpp: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行しかありません。

```cpp:__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つずつ展開していきます。

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

```cpp:__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` は実装が同じなので省略しました。

```cpp: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が完成しました。

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

<hr>
標準ライブラリ読むのは楽しいですね。
今後もどんどん読める範囲から参考にしていきたい。

 
[^1]: 「タプル型を展開して関数の引数として渡す」という説明は[**こちらの記事**](http://secret-garden.hatenablog.com/entry/2017/10/18/235905)から拝借させて頂きました。
[^2]: `decltype(auto)` の説明は[**この記事**](https://cpplover.blogspot.com/2013/08/decltypeauto.html)が詳しいです。
[^3]: ここで参照を取り外す意味がわからない。
[^4]: https://cpprefjp.github.io/reference/functional/invoke.html