variantから値を取り出す(std::holds_alternative + std::get, std::get_if, std::visit)

## 概要
variantを使おうとした際、値の取得法にいくつか選択肢があることに気が付く。（std::holds_alternative + std::get, std::get_if, std::visit）
それらの使い方、使い分けについてまとめる


## 取得方法

各関数の詳細は https://ja.cppreference.com/w/cpp/utility/variant とか見てください


### std::holds_alternative + std::get

「今variantに入ってる型はTか?」を判定する関数 `std::holds_alternative`と
「variantから型Tを取り出す」関数 `std::get` 
による取得

```C++
std::variant<int,std::string> v="cfn";
if(std::holds_alternative<std::string>(v)){
    std::cout<< std::get<std::string>(v);
}
```

`std::get` には 型を指定する代わりにindexを指定することで値を取得する方法もある
しかしこれはマジックナンバーを生み出すことに繋がるため、あまりオススメしない

```C++
std::variant<int,std::string> v="abcd";
if(v.index() == 1){                 // variant.index()は入ってる要素のindexを調べる関数
    std::cout<< std::get<1>(v);     // <== 1ってなんだよ
}
```

### std::get_if

variantが指定した型Tであるなら値へのポインタを、そうでないなら`nullptr`を返す関数
要するに`std::holds_alternative` `std::get` を一度にやる関数である

```C++
std::variant<int,std::string> v="abcd";
if(auto p = std::get_if<std::string>(&v)){  // ポインタで渡す
    std::cout<< *p;
}
```

`std::get`と同じように indexを指定することで値を取得する方法もあるが、
同じ理由で indexによる指定はオススメしない

### std::visit

「渡された関数にvariantが持ってる値を適用する」関数である

すべての型に対し同じ処理を行いたいとき、特に便利

```C++
std::variant<int,double> v=3.14;
// 例1 variantから値を返す
std::string s = std::visit([&](auto x){ return std::to_string(x);}, v);  // variantの値をstringに変換して 返す
std::cout<< s;

// 例2 variantから値を取り出して副作用を起こしたりする
std::string buf;
std::visit([&](auto x){ // xは intかdoubleになる
    buf+=std::to_string(x);
    std::cout << x;
}, v);
```

型によって処理を変えたい場合、 `if constexpr` や 関数のオーバーロードなどを利用すればよい

```C++
//if constexprの場合
std::variant<int,std::string> v="abcd";
std::visit([](auto&& x) {
    using T = std::decay_t<decltype(x)>;
    if constexpr (std::is_same_v<T, int>)
        std::cout << "int: " << x;
    else if constexpr (std::is_same_v<T, std::string>)
        std::cout << "std::string: " << x;
}, v);
```

```C++
// 前準備。 渡された(ラムダ式等の)関数オブジェクトをオーバーロード状にするやつ
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

// コード
std::variant<int,std::string> v="abcd";
std::visit(overloaded {
    [](int n) { std::cout << "int: " << e; },
    [](std::string e) { std::cout << "string: " << e; }
    // Event3にマッチする処理がないのでコンパイルエラーになる
}, v);
```

visitはvariantを複数うけとり処理することもできる

```C++
// a,b 同じ型なら上の関数を、それ以外なら下を呼び出す ちょっとパターンマッチ的な処理
// C++20では `[]<class T>(T a,T b){/*実装*/}` のようにラムダ式でも書けるようになっている(ハズ)
template<class T>
std::optional<bool> hoge(T a,T b){return a == b;}
template<class T, class U>
std::optional<bool> hoge(T,U){ return {};}  

int main(){
    std::variant<int,std::string> v1="abcd";
    std::variant<int,std::string> v2="1234";
    std::visit([](auto a,auto b){return hoge(a,b);}, v1,v2);  
}
```


## 使い分けのヒント

### 共通処理なら
すべての型に共通処理を行いたいなら 
前述のとおり `std::visit` がおすすめ

```C++
std::variant<hoge1,hoge2,hoge3> v;
std::visit([&](auto x){ do_hoge(x);}, v); // すべてに同じ処理をする
```

### ある特定の型のみに対して処理を行いたい

ある型数個のみに処理行いたいなら `std::get_if` を使うのがおすすめ

```C++
std::variant<hoge1,hoge2,hoge3> v;
// hoge2ならなんかやる
if(auto p = std::get_if<hoge2>(&v)){
  do_hoge(*p);
}
// 他はどうでもいい
```

### variantの拡張に強くしたいなら
用途にもよるがvariant の要素というのは増えることもある

```C++
// イベントのキュー  
// 今はEvent1,2だけだが 3を増やしたい。そしていつか4を増やすかもしれない
std::vector<std::variant<Event1, Event2/*, Event3*/>> event_queue;   
```

この時、下のようなコードを書いているとEvent3に関する処理が抜けてしまう。
こういうのはコンパイル時に検出したい

```C++
// 例1 
std::visit([](auto&& x) {
    using T = std::decay_t<decltype(x)>;
    if constexpr (std::is_same_v<T, Event1>)
        std::cout << "event1: " << x;
    else if constexpr (std::is_same_v<T, Event2>)
        std::cout << "event2: " << x;
    // event3の処理がない。だが警告も出ない
}, v);


// 例2 get_ifの場合
if(auto p = std::get_if<Event1>(&v)){
    std::cout<< *p;
}
else if(auto p = std::get_if<Event2>(&v)){
    std::cout<< *p;
}
// event3の処理がない。警告も出ない
```
じゃぁどうすればいいかというと、以下の方法が考えられる

- visitで受けて`if constexpr`の分岐の最後に `else static_assert(false_v<T>);` 書く
- オーバーロードで知らない型を型レベルで拒否するようにすればよい


#### if constexpr + static_assert

前述のとおり 分岐の最後に `else static_assert(false_v<T>);` とでも書いておけばよい

false_vについては この記事などを参照
https://qiita.com/saka1_p/items/e8c4dfdbfa88449190c5 

```C++
// 前準備
template <typename T>
constexpr bool false_v = false;

// コード
std::visit([](auto&& x) {
    using T = std::decay_t<decltype(x)>;
    if constexpr (std::is_same_v<T, Event1>)
        std::cout << "event1: " << x;
    else if constexpr (std::is_same_v<T, Event2>)
        std::cout << "event2: " << x;
    else 
        static_assert(false_v<T>);    // ここに処理が来る(=Event1,2以外の型がくる)とコンパイルに失敗する
}, v);
```
#### オーバーロード

オーバーロードを用いれば型レベルで拒否できる

```C++
// 前準備
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

// コード
std::visit(overloaded {
    [](Event1 e) { std::cout << "event1: " << e; },
    [](Event2 e) { std::cout << "event2: " << e; }
    // Event3にマッチする処理がないのでコンパイルエラーになる
}, v);
```

### std::visit+ (if constexpr OR オーバーロード) どちらを使えばいいのか

- パターンマッチ的なことをやる場合オーバーロードのほうが簡単かもしれない
- 複雑なことをやるならif constexprのほうが面倒が少ない
- 簡単な問題なら大差ないので別にどっちでもいい

オーバーロードで複雑なことをしようと思うと SFINAEを使ったり 優先順位に気を使い始めたりすることになり、あまり簡単ではない
しかし一方、パターンマッチ的なことやSFINAEを使った方が便利な場合はオーバーロードで解決した方がうまくいくこともある

ラムダ式でSFINAE
https://qiita.com/Fuyutsubaki/items/e78a133545d52b84fa97

## メモ

### holds_alternative の使い道
holds_alternative が必要な場面なら全部get_ifでいいんじゃないか？と思うかもしれないが、例えば以下のよう場合必要になるかもしれない

- "型Tではない時" を調べたいとき
 - 正規表現でいう `[^a]` 的なことをやりたいときなど
- 値をgetする意味がない時
  - emptyな型(std::monostateとか)や値に興味がない場合など

構文解析っぽい処理をやってると必要になるかもしれない

### 『variantの拡張に強くしたいなら』別解法
#### でたらめな型を返す
static_assertの代わり適当な型を返すコードを書いておく

```C++
// 例1
if constexpr (std::is_same_v<T, Event1>)
    return 42;
else
    return no_reach_kill{};  // 返す値が int と異なるのでコンパイルエラーを引き起こす
// 例2
if constexpr (std::is_same_v<T, Event1>)
    std::cout << "event1: " << x; // ここで返る型はvoidになる(返る型がない)ので、何かまともな値が返ると
else
    return []{};        // でたらめな型ならラムダ式でも何でもいいよ
```

このようにすることで 他の型が来た時に返す型と違う型をstd::visitから返そうとして、結果コンパイルエラーになる
利点

- false_v、というかtwo phase lookupに関する知識がいらない
- 必要タイプ数が短いかもしれない

欠点

- 出力されるエラーメッセージがgcc・clang共に大分わかりにくい (https://wandbox.org/permlink/WtuVvwAvndJf6zzg)
- 意図がわかりづらい

#### variantの要素数でstatic_assertをする

下記の通り。要素数が変わったとき対応できないことを表明する

```
static_assert(std::variant_size_v<decltype(v)> == 2);
```
コメントで何をどう修正すればいいかを書いておけばこれでもいい気はしてる