[C++] bool 型を強くする YesNo クラス
これは C++ Advent Calendar 2021 の参加記事です。
C++ のプログラムを少しだけ読みやすく、安全にするために作った便利クラス YesNo<T>
と、それを簡単に使えるライブラリ siv::YesNo (Boost Software License 1.0) を公開します。このライブラリは Siv3D の API で長らく使われている類似のクラス s3d::YesNo<T>
を、再利用しやすく切り出したものです。
1. よくある問題
bool
型の引数は、呼び出し側からは意味が分からなかったり、取り違えたりするおそれがあります。
void ToHex(int n, bool uppercase)
{
if (uppercase)
std::cout << "uppercase\n";
else
std::cout << "lowercase\n";
//...
}
void DoTask(bool formatDisk, bool writeLog)
{
if (formatDisk)
std::cout << "Formatting a disk...\n";
if (writeLog)
std::cout << "Writing a log...\n";
}
int main()
{
ToHex(255, true); // この true は何?
bool writeLog = true;
DoTask(writeLog, false); // もしかして取り違えてない?
}
enum class
を使ったコード
2. enum class: bool
で名前や型を与えてあげると問題を解消できますが、if (e)
のように書けない不便が発生します。条件演算子 (?:) でも使えません。
enum class Uppercase: bool { False, True }; // これを
enum class FormatDisk: bool { False, True }; // 毎回記述するのは
enum class WriteLog: bool { False, True }; // 面倒
void ToHex(int n, Uppercase uppercase)
{
if (uppercase) // コンパイルエラー: if (e) はできない
std::cout << "uppercase\n";
else
std::cout << "lowercase\n";
//...
}
void DoTask(FormatDisk formatDisk, WriteLog writeLog)
{
if (formatDisk == FormatDisk::True) // == を使うか,
std::cout << "Formatting a disk...\n";
if (static_cast<bool>(writeLog)) // bool 型にキャストする
std::cout << "Writing a log...\n";
}
int main()
{
ToHex(255, Uppercase::True);
DoTask(FormatDisk::False, WriteLog::True);
}
YesNo<T>
を使ったコード
3. YesNo<T>
を使うと、enum class
のように型や名前を与えつつ、bool
型の値であるかのように if ()
や条件演算子の中で使うことができます。
using Uppercase = YesNo<struct Uppercase_tag>;
using FormatDisk = YesNo<struct FormatDisk_tag>;
using WriteLog = YesNo<struct WriteLog_tag>;
void ToHex(int n, Uppercase uppercase)
{
if (uppercase) // ok
std::cout << "uppercase\n";
else
std::cout << "lowercase\n";
//...
}
void DoTask(FortmatDisk formatDisk, WriteLog writeLog)
{
if (formatDisk) // ok
std::cout << "Formatting a disk...\n";
if (writeLog) // ok
std::cout << "Writing a log...\n";
}
int main()
{
ToHex(255, Uppercase::Yes);
DoTask(FormatDisk::No, WriteLog::Yes);
}
4. 実装
実装はシンプルです。
実質的に struct { bool; }
なので、最適化が有効であればオーバーヘッドは発生しません。
5. 使い方
-
"YesNo.hpp"
をインクルード - (必要に応じて)
using siv::YesNo;
using XXXX = YesNo<struct XXXX_tag>;
-
bool
をXXXX
に、true
,false
をXXXX::Yes
,XXXX::No
に置き換える
# include <iostream>
# include "YesNo.hpp"
using siv::YesNo;
using Uppercase = YesNo<struct Uppercase_tag>;
using FormatDisk = YesNo<struct FormatDisk_tag>;
using WriteLog = YesNo<struct WriteLog_tag>;
void ToHex(int n, Uppercase uppercase)
{
if (uppercase)
std::cout << "uppercase\n";
else
std::cout << "lowercase\n";
//...
}
void DoTask(FormatDisk formaDisk, WriteLog writeLog)
{
if (formaDisk)
std::cout << "Formatting a disk...\n";
if (writeLog)
std::cout << "Writing a log...\n";
}
int main()
{
ToHex(255, Uppercase::Yes);
ToHex(255, Uppercase::No);
Uppercase uppercase = Uppercase::No;
ToHex(255, uppercase);
FormatDisk formatDisk = FormatDisk::Yes;
ToHex(255, formatDisk); // コンパイルエラー
DoTask(FormatDisk::No, WriteLog::Yes);
DoTask(FormatDisk{ false }, WriteLog{ true });
DoTask(WriteLog::Yes, FormatDisk::No); // コンパイルエラー
DoTask(false, true); // コンパイルエラー
}
6. おもな API
▼ true
/ false
を表現する定数 Yes
/ No
.
int main()
{
constexpr FormatDisk f1 = FormatDisk::Yes;
constexpr FormatDisk f2 = FormatDisk::No;
}
▼ bool
型の値からの初期化
int main()
{
constexpr bool b1 = true, b2 = false;
constexpr FormatDisk f1{ b1 };
constexpr FormatDisk f2{ b2 };
}
▼ explicit operator bool()
int main()
{
constexpr FormatDisk formatDisk = FormatDisk::No;
constexpr WriteLog writeLog = WriteLog::Yes;
if (formatDisk) {}
if (formatDisk || writeLog) {}
if (!formatDisk) {}
constexpr int n = writeLog ? 100 : 200;
static_assert(writeLog);
}
▼ 予期しない変換を防ぐため、contextually converted to bool でない文脈では明示的に .getBool()
を使う必要があります。
void EnableLog(bool enable) {}
int main()
{
constexpr FormatDisk formatDisk = FormatDisk::No;
constexpr WriteLog writeLog = WriteLog::Yes;
constexpr WriteLog w1{ formatDisk }; // コンパイルエラー
constexpr WriteLog w2{ formatDisk.getBool() };
constexpr bool b1 = formatDisk; // コンパイルエラー
constexpr bool b2 = formatDisk.getBool();
EnableLog(writeLog); // コンパイルエラー
EnableLog(writeLog.getBool());
}
▼ 同じ型どうしで比較ができます。
int main()
{
constexpr FormatDisk formatDisk = FormatDisk::No;
constexpr WriteLog writeLog1 = WriteLog::Yes;
constexpr WriteLog writeLog2 = WriteLog::No;
if (writeLog1 == writeLog2) {}
if (writeLog1 > writeLog2) {}
if (formatDisk == writeLog1) {} // コンパイルエラー
}
7. Siv3D の API での使用例
# include <Siv3D.hpp> // OpenSiv3D v0.6.3
void Main()
{
// ストップウォッチを即座に開始
Stopwatch stopwatch{ 15s, StartImmediately::Yes };
Image image{ 256, 256, Palette::White };
// アンチエイリアスを有効にして円を画像に描き込む
Circle{ 128, 128, 60 }.paint(image, Palette::Orange, Antialiased::Yes);
// 画像を WebP のロスレス形式で保存
image.saveWebP(U"image.webp", Lossless::Yes);
// ファイルをゴミ箱に送らず削除する
FileSystem::Remove(U"image.webp", AllowUndo::No);
const LineString lines
{
Vec2{ 500, 100 }, Vec2{ 700, 200 }, Vec2{ 600, 500 },
};
// 各点を結んだ連続する線分の長さを計算(終点と始点をつないでリング状にする)
const double length = lines.calculateLength(CloseRing::Yes);
}
8. ライブラリ (Boost Software License 1.0)
今回紹介した YesNo<T>
クラスを簡単に利用できる siv::YesNo ライブラリを下記のリポジトリで公開しました。Boost Software License 1.0 です。不具合や改善案の報告は Issues までお願いします。
Discussion