C++では、式 ~true が単純なビット反転で false になるだろうという期待とは逆に、bool 値はまず整数型に昇格された後に補演算が行われます。真(1)は int になり、そのビット補数は 2 の補数表現において –2 となり、この int を bool に戻す際はブーリアン変換の規則に従い、ゼロでない値は true になります。したがって static_cast<bool>(~true) は true と評価され、これはプライマリソースが対応するアセンブリ出力を示して確認できます。
同じ操作が [[std::bitmask_type]] で注釈された基底型が bool のスコープ列挙型に適用される場合、標準によれば期待される結果も true になります。列挙型のビット補数演算子はまず列挙型をその基底の bool に変換し、その bool を int に昇格させ、補数 (‑2) を計算した後、その結果の int を元の列挙型にキャストします。Clang と MSVC は最終的なキャストにブーリアン変換を適用し、true を生成します。それに対して GCC は逸脱しており、int の値を最下位ビットに切り捨て (‑2 → 0) してからキャストし、その結果 false を生み出します。この違いはプライマリソースに記録されており、挙動の違いを示す Godbolt リンクが掲載されています。
関連する落とし穴として、基底型が固定されていないスコープなし列挙型があります。このとき補演算子は列挙型が表現できる範囲外の値を生成し得て、その結果を列挙型にキャストすると未定義の動作を引き起こします。たとえば、enum nums {zero, one, two, three} は基底型として unsigned int を持ち、~one は int として ‑2 を生じますが、これを nums にキャストしようとすると範囲外となり、標準ではこれが未定義の動作であると定めています。プライマリソースはこのリスクを説明し、問題が発生するのは基底型を明示的に指定していない plain enum のみであり、スコープ列挙型や基底型が固定されている列挙型は well‑defined な範囲を継承するため影響を受けないことを指摘しています。
