【懺悔】カンマ演算子のオーバーロードを使ってしまいました…[void判定編]

# はじめに
以前自分が1度だけカンマ演算子のオーバーロードを使ったことがあったのですが
その時の事例を紹介しようと思います。

### 導入

>むかしむかし、まだC++20で人類がconceptの力を手に入れるよりも前のことです。
あるところに、どうしてもテンプレート型制約がしたい、おじいさんとおばあさんがいました。
おじいさんは山括弧でテンプレートをしに
おばあさんはSFINAEで選択をしに行きました。
すると、どんぶらこ～ どんぶらこ～ と カンマ演算子が流れてきたのです。

# 型制約・メタ関数の実装
Detection Idiomなどで任意のメソッドをもつ型を判定したい際に、
decltypeの中に必要なメソッドをカンマ演算子で並べて記述するといった書き方があります。

```cpp
template<class T,class = void>
struct is_hogefoo : std::false_type
{};
template<class T>
struct is_hogefoo <T, std::void_t<decltype(
    std::declval<T>().hoge(),
    std::declval<T>().foo()
)> > :std::true_type
{};
```
しかし、これだけだと、戻り値の型が何かまでは制約ができていないので
その辺りの条件もつけていきます。

書き方はいろいろありそうですが、例えばこう
```cpp
template<class T>
struct is_hogefoo <T, std::enable_if_t<std::conjunction_v<
    std::is_same<int, decltype(std::declval<T>().hoge())>,
    std::is_same<void, decltype(std::declval<T>().foo())>
 >>> :std::true_type
{};
```
片方は戻り値の型は任意にしようと思ったら
以下みたいになってきます。
```cpp
template<class T>
struct is_hogefoo <T, std::enable_if_t<std::conjunction_v<
    std::is_void<std::void_t<decltype(std::declval<T>().hoge())>>,
    std::is_same<void, decltype(std::declval<T>().foo())>
 >>> :std::true_type
{};
```

これくらいなら、まだいいのかもですが…

**複雑で、よみづらーーーい！！！**
**書くこと多くて、めんどうくさーーーい！！！！！！**
と私は感じました。

## もう少し簡潔に書けないか考える

ここで以下みたいな関数を宣言します(評価しないので定義は不要だよ)

```cpp
template<class T, class U>
auto valid_expr(U&&)->std::enable_if_t<std::is_same_v<T, U>>;
```

そうすると最初の書き方と近い書き方で簡潔にかける
```cpp
template<class T>
struct is_hogefoo <T, std::void_t<decltype(
    valid_expr<int>(std::declval<T>().hoge()),
    std::declval<T>().foo()
)> > : std::true_type
{};
```

しかし、ひとつ大きな問題があります。

**void型の判定には使えない。**

```cpp
valid_expr<void>(std::declval<T>().foo())
```
上記はうまくいきません。なぜなら`foo()`が`void`の場合は関数の引数に渡せないからです。

かといって`void`の時だけ素直な書き方にするのも汚くなる

```cpp
template<class T>
struct is_hogefoo <T, std::void_t<decltype(
    valid_expr<int>(std::declval<T>().hoge()),
    std::enable_if_t<std::is_void_v<decltype(std::declval<T>().foo())>, void*>{}
)> > : std::true_type
{};
```

## この問題を解決してくれたカンマ演算子のオーバーロード

ここで以下のように `void_tester_t` という構造体を導入し、カンマ演算子をオーバーロードしました。(ここも実装は不要)

```cpp
struct void_tester_t
{
    template<class T>
    friend void operator, (T&&, void_tester_t);
};

inline constexpr void_tester_t void_tester{};
```

さらに以下のような`valid_expr`もオーバーロードします
```cpp
template<class T>
auto valid_expr(void_tester_t)->std::enable_if_t<std::is_same_v<T, void>>;
```
### 何が起きるのか解説

```cpp
template<class T>
friend void operator, (T&&, void_tester_t);
```
上記のようなオーバーロードをしたことで
```cpp
(a(), void_tester)
```
を呼んだ際に

1. `a()`が`void`以外の場合
    - カンマ演算子のオーバーロードが呼ばれて、void型が返る
1. `a()`が`void`の場合
    - void型は引数に渡せないので **カンマ演算子のオーバーロードが呼べずに、通常のカンマ演算子として処理**され、後方のvoid_tester_t型が返る

このテクニックにより`void`型かそうでない時かで処理を分岐することができます。
また、このような処理は他の演算子のオーバーロードでは実現できません
(オーバーロードできる演算子で通常`void`に対しても動作する演算子は他にないはず？)

```cpp
 valid_expr<void>((std::declval<T>().foo(), void_tester))
```
今回では上記のような使い方をすることで、`foo()`に戻り値があった場合は、void型が返るので関数にvoidは渡せずに失敗。
voidだった場合は、void_tester_tが返るので以下のオーバーロードに合致で成功といった使い方になります。

```cpp
template<class T>
auto valid_expr(void_tester_t)->std::enable_if_t<std::is_same_v<T, void>>;
```

これにより最終的に以下のような記述が可能になりました。
voidだけ特殊な書き方になるので、ちょっと気持ち悪さも残るけど、記述の複雑さはだいぶスッキリしました。
```cpp
template<class T>
struct is_hogefoo <T, std::void_t<decltype(
    valid_expr<int>(std::declval<T>().hoge()),
    valid_expr<void>((std::declval<T>().foo(), void_tester))
)> > : std::true_type
{};
```
# 余談

そもそもdeclvalがぱっとみの複雑さを残している気もしており
関数の文脈とかでの制約で考えると、簡潔さがわかりやすい

```cpp
template<class T>
auto func(T a) -> decltype(
    valid_expr<int>(a.hoge()),
    valid_expr<void>((a.foo(), void_tester))
);
```

# まとめ

### カンマ演算子のオーバーロード
- カンマ演算子は通常void型も受け取れる数少ない演算子
これを利用してカンマ演算子のオーバーロードをすることで、void型かどうかで評価を分岐できた
  - このテクニックの面白い使い道があるかは不明
- 基本的にvoidの処理分岐には **`std::is_void`で事足りるはず**
    - 今回のような特殊な理由がなければ…
- あんまりカンマ演算子のオーバーロードはよく思われないぞ

### 型制約
- C++20以上を書こう
- conceptを書こう

```cpp
template<class T>
concept hogefoo_callable = requires(T a)
{
    {a.hoge()}->std::same_as<int>;
    {a.foo()}->std::same_as<void>;
};
```

### おじいさん と おばあさん とは何だったのか？
- 自分にもわからない