C++ プログラムを進化させる 4 項目

# はじめに

この記事は、C++ Advent Calendar 2019 の 8 日目の投稿です。

昨日は @h6akh さんの [C++部・ユニットテストの裏技 〜private関数をテストする〜](https://qiita.com/h6akh/items/0a65cf3ca3c735bb63b1) でした。
明日は @mdstoy さんの [next_combinationを実装してみた](https://qiita.com/mdstoy/items/39104b686540c5f1dc6c) になります。

C++20 の機能がどんどん実装されてきていますが、この記事では、C++ 全般の少し使えるかもしれないネタをいくつか紹介しようと思います。タイトルの「C++ プログラムを進化させる〇項目」は、Effective C++ シリーズの副題を流用したものです。4 項目の中身は、小ネタ 2 つと、ツイッターで見つけたネタ 2 つです。また、本文中のサンプルコードでは、読みやすさのために `constexpr` や `noexcept` などを省略しています。

# 項目 1 : `std::forward` にテンプレート引数を指定する理由

完全転送の際に `std::forward` を使うとき、いつもテンプレート引数を指定して使っていると思います。ですが、テンプレート引数をこちらで指定しなくても、C++ にはテンプレート引数を推定する機能があるので、これを使えばいいのではないか、という疑問を持つ方もいるのではないでしょうか。しかし実は、`forward`にテンプレート引数を指定しないと完全転送がうまくされません。

次のようなコードで、このテンプレート引数を推定する `forward` とそうではない `std::forward` とを比較してみます。

```cpp
#include <iostream>
#include <utility>

// forward の、テンプレート引数を推論する実装
template<typename T>
T&& forward_with_deduction(T&& obj)
{
    return static_cast<T&&>(obj);
}

// 引数を転送したい関数
auto f(auto&)       { std::cout << "mached: &" << '\n';}
auto f(const auto&) { std::cout << "mached: const &" << '\n';}
auto f(auto&&)      { std::cout << "mached: &&" << '\n'; }

// テンプレート引数を推論する自作 forward を使って転送
template<typename T>
void forwarder1(T&& obj)
{
    f(forward_with_deduction(obj));
}

// std::forward<T> を使って転送
template<typename T>
void forwarder2(T&& obj)
{
    f(std::forward<T>(obj));
}

int main()
{
    int x;
    const int& y(x);
    int&& z = std::move(x);

    std::cout << "引数の推論あり：\n\n";

                              // 出力
    forwarder1(3);            // mached: & 
    forwarder1(x);            // mached: &
    forwarder1(y);            // mached: const &
    forwarder1(z);            // mached: &
    forwarder1(std::move(x)); // mached: &
    forwarder1(std::move(y)); // mached: const &
    
    std::cout << "\n引数の推論なし：\n\n";

                              // 出力
    forwarder2(4);            // mached: &&
    forwarder2(x);            // mached: &       
    forwarder2(y);            // mached: const &       
    forwarder2(z);            // mached: &
    forwarder2(std::move(x)); // mached: &&
    forwarder2(std::move(y)); // mached: &&
}
```

すると、テンプレート引数を推論する自作の `forward` を使って転送する `forwarder1` は、関数の引数を完全転送できない結果となりました。

### テンプレート引数の推論規則

このような動作になるのは、自作 `forward` のユニヴァーサル参照 `T&&` の推論結果が `std::forward` にテンプレート引数を渡した際のユニヴァーサル参照`T&&`の型と異なることが原因だと考えられます。そこで、まず自作 `forward` である `forward_with_deduction` のテンプレート引数`T`がどのような型に推論されたのかを確かめてみます。テンプレートの型推論の規則は、[Effective Modern C++](https://www.amazon.co.jp/Effective-Modern-%E2%80%95C-11-14%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%92%E9%80%B2%E5%8C%96%E3%81%95%E3%81%9B%E3%82%8B42%E9%A0%85%E7%9B%AE/dp/4873117364?tag=waciatdnd-22&ref=pd_sl_267l67p1y3_e)の項目 1 に詳しく記載されていますので、ここではその記述から必要な箇所を引用します。

```cpp
template<typename T>    // from Effective Modern C++
void f(ParamType pram);

f(expr);
```

上のようなものがあるときに、

>テンプレート引数 `T` の推論は、`expr`の型だけから決定されるのではなく、`ParamType`からも影響をうけ、それは次の 3 通りの場合があります。

>ケース 1. `ParamType` が参照もしくはポインタだが、ユニヴァーサル参照ではない。
>ケース 2. `ParamType` がユニヴァーサル参照である。
>ケース 3. `ParamType` がポインタでも参照でもない。

> Effective Modern C++ 第 1 項より

上記のコードの`forward_with_deduction`の`ParamType`に相当する部分は、ケース 2 のユニヴァーサル参照(`T&&`)です。このとき、次のように `T` が推論されます。

> ケース 2 :

> - `expr`が左辺値ならば、`T` も `ParamType` も左辺値参照と推論される。これは 2 つの意味で特殊である。まず、テンプレートの型推論で、`T`を参照として推論するのはこの場合だけである。もう 1 つは、ParamType の宣言には右辺値参照という形態をとりながら推論される型は左辺値参照となる点である。
> - `expr`が右辺値の場合は、「通常の」規則が適用される(ケース 1)。 

> 同じく、Effective Modern C++ 第 1 項より

冒頭のコードでは、`forward_with_deduction(obj)` の関数の引数の式 `obj` は変数の名前なので左辺値(lvalue)です。したがって、テンプレート引数 `T` は左辺値参照となることが分かります。式 `obj` の型は `int`(もちろん、変数`obj`の型は`int&&`です。式の場合は最終的に非参照型をもちます。[^1])なので、結果としてテンプレート引数`T`は`int&`と推論されています。

[^1]: [[expr.type]](http://eel.is/c++draft/expr.type)を参照

### `T&&`の型

ユニヴァーサル参照 `T&&` 型は、`T` が左辺値参照型に推論されたときに、参照の圧縮[^2]によって `T&` となります。結果として、`forward_with_deduction` 内で使っている型 `T&&` は常に左辺値参照となってしまうため、テンプレート引数を推論するような `forward` は書けないということが分かりました。テンプレート引数 `T` をこちらから指定すれば、ユニヴァーサル参照 `T&&` はテンプレート引数 `T` に左辺値参照を渡したときに左辺値参照、そうでないときには右辺値参照となり期待される動作をすることが分かります。

[^2]: Effective Modern C++ 項目 28、または[右辺値参照～完全転送まで100％理解するぞ！ part5 参照の圧縮(reference collapsing) - スーパーハカー幼女 Yuki公主](http://murasame-labo.hatenablog.com/entry/2017/05/17/030821)を参照

### `std::forward<T>` と `std::forward<decltype(obj)>` に違いはない

ところで、`std::forward` のテンプレート引数を渡す際は `std::forward<T>` と `std::forward<decltype(obj)>` のように 2 通りの指定ができますが、このどちらを指定しても実際の動作に違いはありません。ここで、`std::forward` は、例えば gcc では次のように実装されていました。

```cpp:from_gcc
template<typename _Tp>
  constexpr _Tp&&
  forward(typename std::remove_reference<_Tp>::type& __t) noexcept
  { return static_cast<_Tp&&>(__t); }

template<typename _Tp>
  constexpr _Tp&&
  forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
  {
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
	          " substituting _Tp is an lvalue reference type");
    return static_cast<_Tp&&>(__t);
  }
```

冒頭のコードの、`forwarder2(4)`, `forwarder2(std::move(x))`, そして `forwarder2(std::move(y))` を呼び出す際に、`std::forward<T>` のようにテンプレート引数を指定したときに gcc の `std::forward` の実装の `_Tp` は `int` になります。また、`std::forward<decltype(obj)>` のように指定した際は `_Tp` は `int&&` になります。しかし、`_Tp&&` はどちらも `int&&` となるので、テンプレート引数に`T`,`decltype(obj)`どちらを指定しても`std::forward`は問題なく動作していることが分かります。

# 項目 2 : ラムダ式の中でキャプチャした変数を move したいときには、ラムダ式を mutable で修飾する

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ラムダ式内で move してもコピーされてしまうの、これ結構やらかしてる気がする <a href="https://t.co/TJNS1noGe3">https://t.co/TJNS1noGe3</a></p>&mdash; std::めるぽん (@melponn) <a href="https://twitter.com/melponn/status/1138319074281181185?ref_src=twsrc%5Etfw">June 11, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

上記の、めるぽんさんのとても興味深いツイートを紹介します。ラムダ式の中でキャプチャした変数を move するようなときには、ラムダ式に `mutable` を指定しないと期待した動作にならないというものです。以下に、ツイートのリンク先のコードと出力を引用させていただきます。

```cpp
#include <utility>
#include <iostream>

struct A {
  A() { std::cout << "A::A()" << std::endl; }
  A(const A&) { std::cout << "A::A(const A&)" << std::endl; }
  A(A&&) { std::cout << "A::A(A&&)" << std::endl; }
  A& operator=(const A&) { std::cout << "A::operator=(const A&)" << std::endl; return *this; }
  A& operator=(A&&) { std::cout << "A::operator=(A&&)" << std::endl; return *this; }
};

void f(A) {
  std::cout << "f(A)" << std::endl;
}

int main() {
  A a;
  
  auto g = [a = std::move(a)]() {
    std::cout << "lambda g" << std::endl;
    // move したつもりなのにコピーされる
    f(std::move(a));
  };
  g();
  
  auto h = [a = std::move(a)]() mutable {
    std::cout << "lambda h" << std::endl;
    // これならOK
    f(std::move(a));
  };
  h();
}
```

```:出力
A::A()
A::A(A&&)
lambda g
A::A(const A&)
f(A)
A::A(A&&)
lambda h
A::A(A&&)
f(A)
```

`mutable` が使用されていないラムダ式は、`operator()` が `const` 修飾されており、キャプチャしたオブジェクトを変更することができません。上の例コードでは、関数内ではキャプチャしたオブジェクト `a` に `const` がついているようなものだと考えられます。

```cpp
const A a;
auto b = std::move(a); // コピー代入が行われる
```

上のコードのように、const 修飾されたオブジェクトを move しようとしてもコピーされてしまいます。

ラムダ式内でキャプチャした変数を move する際には `mutable` を使用すると、期待通りの動作になるようです。


# 項目 3 : 総称ラムダは、高階関数の引数に便利

高階関数の引数にする際に総称ラムダを用いると、戻り値型や引数型のテンプレート引数を指定しなくても良いので便利です。テンプレート関数を引数に取れるようなテンプレート関数を定義する際などは、テンプレートの定義が煩雑になりがちで、さらに少なくとも戻り値型はこちらから指定する必要があるようです。[^3] 以下に、簡単な比較を載せます。

[^3]: [オーバーロードされた関数へのポインタ / 関数テンプレートへのポインタ - Cry's Blog](https://blog.cryolite.net/entry/01000226/p1) 参照。[コードの例](https://wandbox.org/permlink/UjRHM3S5KXGtYfwM)

```cpp
#include <iostream>
#include <tuple>
#include <functional>

template <typename T, typename ... U>
auto high_order_fn(T && func, U &&... args) // 高階関数
{
    return std::invoke(std::forward<T>(func), std::forward<U>(args)...);
}

// 総称ラムダ
auto lambda = [](auto&& x){ std::cout << x << '\n'; };

// テンプレート関数
template <typename Arg>
void fn(Arg&& x)
{
    std::cout << x << '\n';
}

int main()
{
    high_order_fn(lambda, "Hello, World!");
    // high_order_fn(&fn, "Hello, World!"); エラー！
    high_order_fn(&fn<std::string>, "Hello, World!"); // この例では、特殊化（実体化したもの）を渡せば OK
}
```

### 総称ラムダにコンパイル時引数を持たせる

テンプレート関数の非型テンプレートパラメータで、コンパイル時に関数に引数を取らせることができました。総称ラムダで同じようなことをするには、総称ラムダをテンプレート変数にします。

```cpp
#include <iostream>
#include <tuple>
#include <functional>

template <typename T, typename ... U>
auto high_order_fn(T && func, U &&... args) // 高階関数
{
    return std::invoke(std::forward<T>(func), std::forward<U>(args)...);
}

// 総称ラムダ(コンパイル時引数付き)
template <auto x>
auto lambda = [](auto&& tup){ std::cout << std::get<x>(tup) << '\n'; };

int main()
{
    auto tup = std::make_tuple(1, 3.14, "Hello, World!");
    high_order_fn(lambda<2>, tup); // Hello, World!
}
```

また、C++17 までは、ラムダをテンプレート変数にしてしまうと関数の中に書くことができませんでしたが、C++20 から、総称ラムダのテンプレート構文が追加されたおかげで、関数の中にコンパイル時引数をとるラムダが書けるようになりました。

```cpp
#include <iostream>
 
int main() 
{
    // template <auto x>
    // auto l = []{ return x; }; // ここには書けない
    
    auto f = []<auto x>(){ return x; }; // 書ける！
    auto a = f.operator()<7>();
}
```

### ラムダのオーバーロードがしたい

`boost::hana::overload`を使うと、オーバーロードのようなことができます。次のコードのように、オーバーロードしたいラムダ式を `boost::hana::overload` のコンストラクタに渡すことで構築したオブジェクトを使って、関数呼び出しを行います。 

```cpp
#include <boost/hana/functional/overload.hpp>
#include <iostream>
#include <string>
#include <functional>

template <typename Fun, typename ... Args>
auto high_order_fn(Fun&& fn, Args ... args)
{
   return std::invoke(std::forward<Fun>(fn), std::forward<Args>(args)...);
}

int main()
{
    auto on_string  = [](std::string const& s) {
        std::cout << "matched std::string: " << s << std::endl;
    };

    auto on_int = [](int i) {
        std::cout << "matched int: " << i << std::endl;
    };

    auto on_other = [](auto x) {
        std::cout << "matched other: " << x << std::endl;
    };
    
    auto f = boost::hana::overload(on_int, on_string, on_other);

    high_order_fn(f, 3.14); // 出力 : matched other: 3.14
}
```

[追記]

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ラムダのオーバーロードは boost::hana 使えなくても実は簡単に定義できる…<a href="https://t.co/Ud3Dz4NJBZ">https://t.co/Ud3Dz4NJBZ</a><br><br>C++ プログラムを進化させる 4 項目 <a href="https://t.co/eCsCfuhYzb">https://t.co/eCsCfuhYzb</a> <a href="https://twitter.com/hashtag/Qiita?src=hash&amp;ref_src=twsrc%5Etfw">#Qiita</a></p>&mdash; omni 鳥頭 (@kariya_mitsuru) <a href="https://twitter.com/kariya_mitsuru/status/1203705250827268101?ref_src=twsrc%5Etfw">December 8, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

Twitter で、kariya さんがラムダのオーバーロードを簡単に定義できる方法を投稿して下さいました。そのコードを紹介させていただきます。

```cpp
#include <iostream>
#include <string>
#include <functional>

template<typename... Ts>
struct overload : Ts... {
    using Ts::operator()...;
};

template<typename... Ts>
overload(Ts...) -> overload<Ts...>;

template <typename Fun, typename ... Args>
auto high_order_fn(Fun&& fn, Args ... args)
{
   return std::invoke(std::forward<Fun>(fn), std::forward<Args>(args)...);
}

int main()
{
    auto on_string  = [](std::string const& s) {
        std::cout << "matched std::string: " << s << std::endl;
    };

    auto on_int = [](int i) {
        std::cout << "matched int: " << i << std::endl;
    };

    auto on_other = [](auto x) {
        std::cout << "matched other: " << x << std::endl;
    };

    auto f = overload{on_int, on_string, on_other};

    high_order_fn(f, 3.14); // 出力 : matched other: 3.14
}
```

### C++20 のテンプレートラムダの、テンプレートパラメータを直接指定する呼び出し方

上記にも少し書いたように、C++20 から、総称ラムダにテンプレート構文が追加されたり、評価されない場所(例えば `decltype()` のなか)でラムダ式がかけるようになりました。しかし、テンプレートパラメータを直接指定する際にその方法が少し分かりずらいと思われるので、下に例を載せます。ポイントは、テンプレートなのはラムダのクロージャ型(クラス型)そのものではなくて、その `operator()` だというところです。

```cpp
#include <iostream>
#include <boost/type_index.hpp>

using lambda1_t = decltype([]<typename T>()
                           { 
                               std::cout << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl; 
                           });

auto lambda2 = []<auto x>()
               { 
                   std::cout << x; 
               };

int main()
{
    // lambda1_t<int>{}(); ← こうではなくて、次のように書く！
    lambda1_t{}.operator()<int>(); // 出力 : int
    
    lambda2.operator()<3>(); // 出力 : 3
}
```

### C++20 から、関数の引数の型に `auto` が使えます

また、このトピックとは直接関係ないですが、C++20 から普通の関数の引数に `auto` が使えるようになりとても便利になります。例えば、上記の`high_order_fn`は、次のように書けます。

```cpp
auto high_order_fn(auto && func, auto &&... args)
{
    return std::invoke(std::forward<decltype(func)>(func), std::forward<decltype(args)>(args)...);
}
```

ただし、gcc で試したところ、総称ラムダで使えていた場所を置き換えることはできないようです。

```cpp
#include <iostream>
#include <tuple>
#include <functional>

template <typename T, typename ... U>
auto high_order_fn(T && func, U &&... args) // 高階関数
{
    return std::invoke(std::forward<T>(func), std::forward<U>(args)...);
}

// 総称ラムダ
auto lambda = [](auto&& x){ std::cout << x << '\n'; };

// 引数の型が auto な関数
void fn(auto&& x)
{
    std::cout << x << '\n';
}

int main()
{
    high_order_fn(lambda, "Hello, World!");
    // high_order_fn(&fn, "Hello, World!"); // エラー！
}
```

# 項目 4 : テンプレート引数を取得する

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">これは嘘です。<br>このままだと全て右辺値になって返ってくるので、こんな感じで微妙なのを書きました。<br>C++書ける人がスマートにしてくれるはずなのです。<a href="https://t.co/ahhPZr0KDx">https://t.co/ahhPZr0KDx</a></p>&mdash; ゆかたゆ (@yukata_yu) <a href="https://twitter.com/yukata_yu/status/1196714238946893824?ref_src=twsrc%5Etfw">November 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

非型を除くテンプレートパラメータを取得できる、ゆかたゆさんのツイートのコードが Twitter に投稿されていました。とても有用なコードだと思われるので、ここで紹介させていただきます。

[追記]

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://twitter.com/yukata_yu?ref_src=twsrc%5Etfw">@yukata_yu</a> <a href="https://t.co/WRP7YRcfZU">https://t.co/WRP7YRcfZU</a></p>&mdash; いなむのみたまのかみ (@mitama_rs) <a href="https://twitter.com/mitama_rs/status/1206874618968993792?ref_src=twsrc%5Etfw">December 17, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

Twitter で、上記のコードを関数テンプレートに対応させたコードが投稿されていました。そこで、そのコードを次に紹介させていただきます。

```cpp
#include <iostream>
#include <boost/type_index.hpp>
#define typeof(...) boost::typeindex::type_id_with_cvr<__VA_ARGS__>().pretty_name()
// unwrap_helper_idx
template<unsigned int i, class U, class... Args>
struct unwrap_helper_idx_getter: unwrap_helper_idx_getter<i-1, Args...>{};

template<class U, class... Args>
struct unwrap_helper_idx_getter<0, U, Args...>{ using type = U; };

template<unsigned int i, template<class...> class U, class... Args>
auto unwrap_helper_idx_impl(const U<Args...>&) -> unwrap_helper_idx_getter<i, Args...>;

template<unsigned int i, class... Args, class R>
auto unwrap_helper_idx_impl( R(*fn)(Args...) ) -> typename unwrap_helper_idx_getter<i, Args...>::type;

template<unsigned int i, class U>
using unwrap_helper_idx = typename decltype(unwrap_helper_idx_impl<i>(std::declval<U>()))::type;

// tester
template<class A, class B, class C>
struct TestStruct { };

template<class A, class B, class C>
int test_func(A, B, C) {}

int main()
{
    TestStruct <int, char&, float&&> val;

    std::cout << typeof(unwrap_helper_idx<0, decltype(val)>) << std::endl;
    std::cout << typeof(unwrap_helper_idx<1, decltype(val)>) << std::endl;
    std::cout << typeof(unwrap_helper_idx<2, decltype(val)>) << std::endl;
    std::cout << typeof(decltype(unwrap_helper_idx_impl<2>(test_func<int, char&, float&&>))) << std::endl;
}
```

```:出力
int
char&
float&&
float&&
```

上記のように、エイリアステンプレート`unwrap_helper_idx`のテンプレートパラメータの 1 番目に取得したいテンプレート引数の 0 ベースのインデックスを指定し、2 番目にテンプレート引数を取得したいテンプレートを指定すると、取得したい型が得られるようになっているようです。

# まとめ
・完全転送のためには、`std::forward`にテンプレート引数を指定する必要がある。
・ラムダ式の中で `std::move` を使うときは、ラムダを `mutable` で修飾する。
・総称ラムダは高階関数の引数に便利。
・非型テンプレート引数がないテンプレートのテンプレート引数を取得することができる。

最後に、ツイートを紹介させていただいためるぽんさん、ゆかたゆさん、kariya さん、いなむのみたまのかみさん、ありがとうございました。
それでは、良い C++20 ライフを！