C++20での"bitwise operation between different enumeration types is deprecated"を力技でねじ伏せる

TL;DR
======
- C++20では、異なるenum同士などの演算はdeprecatedなので使わないでね！
- bad knowhowとして、enumの先頭に"+"をつけると、warningは出なくなる
- でも（気軽に）良い子は真似しないでね！容量用法を守って正しく使ってね！

自己紹介
=======
- 元経歴10年超の組込系エンジニア。C/C++がメインでバリバリOSありなし関わらずお仕事してた
- いきなり全くの畑違いに左遷され、大変な事になっております　ま、それはよいとして

OpenCVのC++20対応で問題になったenum演算
=====================================

例えば、上位bitと下位bitを別のenumに定義して、ORedなどで使いたい事ってありますよね（昔のソースコードがそうなっていて困っている事の方が多いかな？）

```c++
#include <iostream>

enum {
  TYPE_JPG = 0x10000000,
  TYPE_PNG = 0x20000000,
};

enum {
  JPG_BASE = 0x0000,
  JPG_PROG = 0x0001,
};

int main()
{
  auto mode = TYPE_JPG | JPG_BASE;
  std::cout << mode << std::endl;
  return 0;
}
```

このコードはGCCでもclang++でも、C++17でbuildしても怒られないが、C++20でbuildするとwarningが発生する。

```bash
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ clang++ main.cpp -o a.out --std=c++17
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ clang++ main.cpp -o a.out --std=c++20
main.cpp:15:24: warning: bitwise operation between different enumeration types ('(unnamed enum at main.cpp:3:1)' and '(unnamed enum at main.cpp:8:1)') is deprecated [-Wdeprecated-anon-enum-enum-conversion]
   15 |   auto mode = TYPE_JPG | JPG_BASE;
      |               ~~~~~~~~ ^ ~~~~~~~~
1 warning generated.

kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ g++ main.cpp -o a.out --std=c++17
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ g++ main.cpp -o a.out --std=c++20
main.cpp: In function ‘int main()’:
main.cpp:15:24: warning: bitwise operation between different enumeration types ‘<unnamed enum>’ and ‘<unnamed enum>’ is deprecated [-Wdeprecated-enum-enum-conversion]
   15 |   auto mode = TYPE_JPG | JPG_BASE;
      |
```

どうして怒られるのか？
===================

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1120r0.html

https://cpprefjp.github.io/lang/cpp20/deprecate_arithmetic_conversion_on_enumerations.html

> 三方比較演算子の導入にともない、一方のオペランドが列挙型である場合の算術演算での**暗黙の**算術変換を非推奨とする。

 はい、ここ重要です。「暗黙の」算術変換を非推奨とする、とあります。

`enum` v.s. `別のenum`、だけでなく、 `enum` v.s. 整数、 `enum` v.s. 浮動小数 など、なんでもエラーになります。

まじめにキャストしてもいいんだけどね
=================================
最初、ちゃんと整数型へキャストすることで、問題を回避しよう、と思いました。

でも、なんだかな、といまいち感が否めないのも否定しきれませんでした。

```cpp
int main()
{
  auto mode = static_cast<int>(TYPE_JPG) | static_cast<int>(JPG_BASE);
  std::cout << mode << std::endl;
  return 0;
}
```

たった1つの…ではないけど、冴えたやりかた
=====================================

OpenCV communityにpull requestを出したところ「+をつければいいよ」とのアドバイス。ほほお！

```diff_cpp
int main()
{
- auto mode = TYPE_JPG | JPG_BASE;
+ auto mode = +TYPE_JPG | JPG_BASE;
  std::cout << mode << std::endl;
  return 0;
}
```

たったこれだけでございます！

```
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ g++ main.cpp -o a.out --std=c++17
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ g++ main.cpp -o a.out --std=c++20
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ clang++ main.cpp -o a.out --std=c++17
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ clang++ main.cpp -o a.out --std=c++20
```

そして後から見ると、cpprefjpさまにもこの記載がありましたね・・・

https://cpprefjp.github.io/lang/cpp20/deprecate_arithmetic_conversion_on_enumerations.html

```cpp
enum E1 { e };
enum E2 { f };

bool b = e <= 3.7; // C++20から非推奨
int k = f - e;     // C++20から非推奨
int x = +f - e;    // OK
```

その他の解決策も考える
====================
こういうトリックをやると、警告を出していた意味が見えなくなるため、もう少しまともな対策も考える。

（回避策１）：ORedしたい場合、１つの定義内にまとめる
-------------------------------------------------
ORedして使いたい列挙子は、１つの定義内にまとめれば、そもそも警告は出ない。こちらのほうが推奨かしら？ただし、どんどん肥大化し、意味でわけられなくなる、というデメリットもある。

```cpp
enum {
  TYPE_JPG = 0x10000000,
  TYPE_PNG = 0x20000000,
  JPG_BASE = 0x0000,
  JPG_PROG = 0x0001,
};
```

（回避策２）整数などと一緒に計算する可能性がある定数定義にenumを使う場合、`constexpr`への置き換えも考える
-------------------------------------------------------------------------------------------------

```cpp
constexpr auto XY_ONE = 1 << 6;

int px = p.x / XY_ONE;
int px = p.y / XY_ONE;
```

だけどね・・・
============
OpenCV pullrequestにいれたら、やっぱり"+" trickは分かりにくいから、static_cast<>しようぜ！というレビューをもらってしまいました。あばばばばばばばばば（ここにきて頭が痛い）

ということで。

- 他enumとORedで使う場合は、cast先するべき型がそもそもないので、 + trickを許して！
- doubleやfloatと演算する場合は、static_cast<>(ENUM) するね！

というあたりを落としどころにしてみた。

（12/14 追記）力技ではない、正攻法
--------------------------------
@yumetoda さんより、コメント欄で教えていただきましたので、こちらも紹介をさせていただきます。

> enum classにして明示的にoperator overloadを書くという手もあると思いますね。

https://qiita.com/hon_no_mushi/items/072f27521c13782b1a04#comment-f4349ff4d226890e21b1

そう！こういう正攻法が使えれば良かったのですが・・・

- OpenCV projectの場合、"C++11でも、C++17でも、今後は、C++20/23/26でも、buildしてtestが正常に動作できるように！"ということが求められる。
- C++のソースコードからpython bindingを自動生成したりしており、defineを変更するのは難しいらしい（内部処理でAPI関わらない部分なんだけどなあ）

こうした「しがらみ」もあり、抜本的な対策が打てなかったのでございます。

まとめ
======
- C++20から、列挙子は他列挙子や数値と混ぜるな危険でございます！
- "+"をつけることで、ごまかせるみたい。でも分かりにくいから、レビューで怒られるかも！
- 正式対応も色々考えていきましょう！


以上です、ご精読、ありがとうございました！