知人に variant の選択肢(alternative)を狭めるクラスを書かされた話

# 書いたもの
https://github.com/yaito3014/variant_view

# 概要
プログラマの知人と通話をしていた時、あるコードパスで `variant` の選択肢 (alternative) が限られる際にその限られた選択肢にのみ対応する visitor を渡したい需要があるという話をされ、そういったクラスを書いてくれ(意訳)と言われたので(一緒に)書きました。
```cpp:example.cpp
#include <iostream>
#include <string>
#include <variant>
#include <yk/util/overloaded.hpp>
#include <yk/variant_view.hpp>

int main() {
  std::variant<int, double, std::string> variant = 42;

  // 本来書くべき visit
  std::visit(yk::overloaded{
                 [](int x) { std::cout << "int : " << x << std::endl; },
                 [](double x) { std::cout << "double : " << x << std::endl; },
                 [](const std::string& s) { std::cout << "string : " << s << std::endl; },
             },
             variant);

  // int か double かしか取らないと仮定する
  auto variant_view = yk::make_variant_view<int, double>(variant);

  // std::string を省略してもよい
  yk::visit(yk::overloaded{
                [](int x) { std::cout << "int : " << x << std::endl; },
                [](double x) { std::cout << "double : " << x << std::endl; },
            },
            variant_view);
}
```

## コピーコストがほぼ掛からない

「`variant` の選択肢を狭めたい」と思って検索するとヒットするのは `variant<A, B, C>` から `variant<A, B>` への変換関数などでありコピーコストへの懸念が残りますが、 `variant_view` では元の `variant` を参照するだけとなります。

```cpp
#include <cstddef>
#include <type_traits>
#include <utility>
#include <variant>
#include <yk/variant_view.hpp>

template <class T, class... Us>
struct is_in_types : std::disjunction<std::is_same<T, Us>...> {};

template <class... To, class... From, std::enable_if_t<std::conjunction_v<is_in_types<To, From...>...>, std::nullptr_t> = nullptr>
constexpr std::variant<To...> narrow_variant(const std::variant<From...>& variant) {
  return std::visit(
      [](auto&& arg) -> std::variant<To...> {
        if constexpr (std::is_constructible_v<std::variant<To...>, decltype(arg)>) {
          return std::variant<To...>{std::forward<decltype(arg)>(arg)};
        } else {
          throw std::logic_error("bad variant cast");
        }
      },
      variant);
}

int main() {
  std::variant<int, double, std::string> variant;
  auto narrowed = narrow_variant<int, double>(variant);     // 元の variant からコピー
  auto view = yk::make_variant_view<int, double>(variant);  // 元の variant を参照
}
```

## ミスがあった際の実行時チェック

特定の型のみ考慮したい場合に大抵は何もしないオーバーロードを追加してコンパイルエラーを握りつぶすと思われますが、想定していない型が現れた際には何らかの実行時エラーがあるべきです。
`variant_view` ではその際に `std::bad_variant_access` 例外を送出します。

```cpp
std::variant<int, double, std::string> variant = "foo";

std::visit(yk::overloaded{
                [](int x) { std::cout << "int : " << x << std::endl; },
                [](double x) { std::cout << "double : " << x << std::endl; },
                [](auto) { /* (エラー処理も含め)何もしない */ },
            },
            variant);

// => 何もエラーが出ずに終了
```

```cpp
std::variant<int, double, std::string> variant = "foo";

auto variant_view = yk::make_variant_view<int, double>(variant);

yk::visit(yk::overloaded{
              [](int x) { std::cout << "int : " << x << std::endl; },
              [](double x) { std::cout << "double : " << x << std::endl; },
          },
          variant_view);

// => std::bad_variant_access 例外が投げられる
```

## `std::variant` との互換性
`variant_view` は `std::variant` の持つ機能をほぼ網羅しているので、`std::variant` と同じように扱うことができます。
ただし `std::get` や `std::holds_alternative` へのオーバーロードの追加は規格的にダメなので独自の `yk::get` や `yk::holds_alternative` を用意しています。

## `boost::variant` への対応
件の知人は元々 `boost::variant` (特にその再帰できる機能)を使っていたため、そちらにも対応しています。今のところ `boost::variant2::variant` には対応していません。

# 機能概要

ソースコードは読めば分かるように書いているつもりですが、ドキュメント化という意味でも機能を書き下していきます。

## `variant_view`
メインのクラスです。基本的に `std::variant` のインターフェースに沿っていますが、 `get` についてだけは変則的です(後述)。

### コンストラクタ

#### デフォルトコンストラクタ
```cpp
variant_view();
```
空の `variant_view` を構築します。基本的に使うことはありません。

#### `variant` からの構築
```cpp
variant_view(Variant& variant);
variant_view(variant_type&& variant);
```
一番使うコンストラクタですが、基本的には後述の `make_varianT_view` を使うことになると思います。

#### 他の `variant_view` からの変換
```cpp
variant_view(const variant_view<V, Us...>& other);
```
選択肢を狭めるような変換しか許さないように制約を設けています。

### `base`
```cpp
const variant_type& base() const;
variant_type& base() const;
```
`variant_view` が参照している `variant` への参照を返します。

### `subview`
```cpp
variant_view<Variant, Us...> subview() const;
```
同じ `variant` を参照しつつ、さらに選択肢を狭めます。

### `invalid`
```cpp
bool invalid() const
```
デフォルト構築した `variant_view` はどの `variant` も参照しないため無効な状態であり、そのような状態であることを確かめるためのメンバ関数です。

### `visit`

```cpp
template <class Visitor>
decltype(auto) visit(Visitor&& vis) const;

template <class Res, class Visitor>
Res visit(Visitor&& vis) const;
```
メンバ関数版 `visit` です。

### `index`
```cpp
std::size_t index() const;
```
`std::variant` の `index` および `boost::variant` の `which` を**そのまま**呼びます。
狭めた選択肢に対する index でないことに注意してください。

### 比較演算子
宣言は省略しますが、元の `variant` の比較演算子を**そのまま**呼び出します。

### `sizeof...(Ts) == 1` の特殊化について
```cpp
decltype(auto) operator*() const;
auto operator->() const;
operator bool() const;
```
選択肢が一つまで狭まった時にわざわざ `get` や `visit` を呼ぶのは手間なので、ポインタライクな簡易的なアクセスを追加で提供します。


## `get`
```cpp
template <class T, class VariantView>
decltype(auto) get(VariantView&& view);

template <std::size_t I, class VariantView>
decltype(auto) get(VariantView&& view);

template <class T, class VariantView>
decltype(auto) get(const VariantView* view);

template <std::size_t I, class VariantView>
decltype(auto) get(const VariantView* view);
```
`std::get` と同様ですが、 **`std::get_if` 相当の機能も含んでいます**。これは `boost::get` のインターフェースと合わせてあります。`variant_view` だけでなく `std::variant` や `boost::variant` にも使えます。

## `visit`
```cpp
template <class Visitor, class Variant>
decltype(auto) visit(Visitor&& vis, Variant&& variant);

template <class Res, class Visitor, class Variant>
Res visit(Visitor&& vis, Variant&& variant);
```
非メンバ関数版 `visit` です。 `variant_view` だけでなく `std::variant` や `boost::variant` にも使えます。

## `make_variant_view`
`variant_view` を作るヘルパ関数です。CTAD では限界があるので、使いやすさのために提供しています。

```cpp
std::variant<int, double, std::string> variant = 42;

// int, double に狭める
auto view_1 = yk::make_variant_view<int, double>(variant);

// int のみに狭める
auto view_2 = yk::make_variant_view<int>(variant);

// 狭めずに variant_view を作る
auto view_3 = yk::make_variant_view(variant);
```

## `holds_alternative`

```cpp
template <class T, class Variant>
bool holds_alternative(const Variant& variant);
```
`variant` や `variant_view` が型 `T` を保持しているかどうかを返します。
空の `variant_view` に対しても `false` を返します。

# 最後に
実用できそうなら教えて下さい。バグ取りも多分します。