このページはC++17に採用された言語機能の変更を解説しています。
のちのC++規格でさらに変更される場合があるため関連項目を参照してください。
概要
C++17ではbool
型に対する前置および後置のoperator ++
を削除する。
bool
型に対する前置および後置のoperator ++
とはC++98の時点で非推奨になっていた機能である。
具体的にどのような働きをするのかというと、以下のように値をtrue
に書き換える機能をもつ。
#include <iostream>
int main()
{
bool b = false;
const bool b1 = ++b;
std::cout << std::boolalpha << b1 << std::endl; // => true
const bool b2 = ++b;
std::cout << std::boolalpha << b2 << std::endl; // => true
}
ここで、前置のoperator ++
は、以下のように置き換えられる:
#include <iostream>
int main()
{
bool b = false;
b = true;
std::cout << std::boolalpha << b << std::endl; // => true
b = true;
std::cout << std::boolalpha << b << std::endl; // => true
}
一方後置のoperator ++
を使う次のようなコードは、以下のようにC++14で標準ライブラリに導入されたstd::exchange()
を利用して書き換えることができる。
#include <iostream>
void f(bool b)
{
std::cout << std::boolalpha << b << std::endl;
}
int main()
{
bool b = false;
// 関数fには変数bの現在の値であるfalseの値が渡される
f(b++); // => false
std::cout << std::boolalpha << b << std::endl; // => true
}
#include <iostream>
#include <utility>
void f(bool b)
{
std::cout << std::boolalpha << b << std::endl;
}
int main()
{
bool b = false;
f(std::exchange(b, true)); // => false
std::cout << std::boolalpha << b << std::endl; // => true
}
仕様
これまで、operator ++
の定義は、bool
型のときはtrue
に変更する、operator --
の定義はbool
型を除く、というように例外規定されていた(§ 8.2.6 expr.post.incr / § 8.3.2 expr.pre.incr)。
C++17ではこれらが削除され、operator ++
の定義(§ 8.2.6 expr.post.incr / § 8.3.2 expr.pre.incr)に、bool
型を除く、という例外規定が追加された。
前置のoperator ++
とoperator +=
の呼び出し(例えば++a
とa+=1
)が等価にならない例に、bool
型の場合、という文面があったが、C++17で削除された(§ 8 expr)。
また、組み込みのoperatorのリストのoperator ++
に関する文面に、bool
型を除く、という例外規定が追加された(§ 16.6 over.built)。
この機能を削除するに至った背景・経緯
この項は十分な出典が存在せず推測でしかないことに注意して読み進めてほしい。
もともとC++の前身であるC言語(ANSI C89)にはbool
型は存在しなかった。
そのために、真理値を表すためにbool
型の代わりとしてint
型やchar
型、unsigned char
型で代用する例が見られた。
int main(void)
{
int flag = 0;
/* do something */
if(flag){
/* do something when flag is true*/
}
return 0;
}
つまり、非0をtrue
、0をfalse
として扱う。
ここで、「初回のみ最大火力で装置をテストし、2回目以降は通常の火力を発射する」シナリオを考えてみよう。
#include <iostream>
void test_firepower() {
// 最大出力で火力を試す
std::cout << "最大火力" << std::endl;
}
void fire() {
// 通常の火力で ”処理” をする
}
int main()
{
int tested = 0;
while (true) {
if (!tested) {
test_firepower();
} else {
fire();
}
tested++;
}
}
これはループのはじめのみtest_firepower
関数(最大火力で装置をテスト)が呼び出されることを期待している。しかし期待通りには動かない。
tested
がint
型の最大値になったときif文の条件評価が行われることを考えよう。
tested
のインクリメントはオーバーフローするので未定義動作になるが、殆どの環境で2の補数表現を使っているため、int
型の最小値になる。
すると、tested
の値が再び0まで加算された際に再びtest_firepower
関数が呼び出されてしまう。
これによく似たバグで少なくとも6つの過度の放射線被曝事故を引き起こし、3人が死亡した例がある。
Therac-25はカナダ原子力公社(AECL)とフランスCGR-MeV社によって開発・製造された放射線療法機器である。
この装置のソフトウェアのバグの一つに、条件変数を非0にする(=true
にする)ために、インクリメントを使っていたというものがあった。
条件変数はC++でいえばstd::uint8_t
型で、つまり256回に1度オーバーフローを起こして値が0になるために、false
として扱われた。
この結果ほかの条件変数の状態によっては25MeVという通常の100倍のβ線が射出されることがあった。
こうした事故を防ぐためなのかは不明だが、C++のbool
型はインクリメントした際、常にtrue
になるように定められていた。
しかし、そもそも上記のバグを防ぐには、インクリメントではなく単に固定値を代入するようにするべきであり、C++98の時点でdeprecatedになっていたと思われる。
C++14でstd::exchange()
が導入されたことにより、唯一使いみちのあった後置のoperator++
の必要性もなくなり、C++17で削除されたと推測される。
関連項目
参照
- P0002R1: Remove Deprecated operator++(bool)
- P0002R0: Remove Deprecated operator++(bool)
- Core issue 1653: Removing deprecated increment of bool
- N3668: exchange() utility function, revision 3
- history - Why does the boolean type in C++ support ++ but not --? - Software Engineering Stack Exchange
- Leveson, Nancy G.; Turner, Clark S. (July 1993). "An Investigation of the Therac-25 Accidents" (PDF). IEEE Computer. 26 (7): 18–41. doi:10.1109/MC.1993.274940.
テキスト起こし - Therac-25 - Wikipedia