boost-ext/utに見るユーザー定義リテラルと代入演算子のオーバーロードの使い方
はじめに
boost-ext/ut(以下UT)は、C++の軽量な単体テストフレームワークである。
シングルヘッダーライブラリで、マクロフリー、コンパイル時間が短いといったことが売りである。
例えば、次のように使える。
#include "ut.hpp"
auto sum(auto... args) { return (args + ...); }
int main() {
using namespace boost::ut;
"sum"_test = [] {
expect(sum(1, 2) == 3_i);
expect(sum(2, 3) == 6_i);
};
}
これをC++20でコンパイルして実行すると、
Running "sum"...
example.cpp:10:FAILED [5 == 6]
FAILED
===============================================================================
tests: 1 | 1 failed
asserts: 2 | 1 passed | 1 failed
と出力される。
詳しい使い方はREADME等に譲る。
じゃあこの記事は何なんだという話だが、上の例のコード、なにかおかしくないだろうか。
"sum"_test = [] {
expect(sum(1, 2) == 3_i);
expect(sum(2, 3) == 6_i);
};
ここでは「ただの代入」しかしていないように見える。ではなぜプログラムを実行するとテストが実行されるのだろうか、という話をこの記事では解説する。
ユーザー定義リテラル
まず"sum"_test
についている_test
とは何だろうか。これはユーザー定義リテラルと呼ばれるものである。
詳しくは上記記事に譲るが、例えば"hello"s
の型が"std::string"になるsリテラルのようなものを、自分で定義することができる。その際にはアンダースコアから始める必要がある。
この_test
はUTでは以下のように定義されている。
[[nodiscard]] inline auto operator""_test(const char* name,
decltype(sizeof("")) size) {
return detail::test{"test", std::string_view{name, size}};
}
ここでdetail::test
はstructである。
ちなみに最初の方に書いた3_i
や6_i
の_i
もこのユーザー定義リテラルで、これを省くとエラーの時のメッセージが少し不親切になる(5==6だったのがただのfalseになる)。
Running "sum"...
example.cpp:10:FAILED [false]
FAILED
===============================================================================
tests: 1 | 1 failed
asserts: 2 | 1 passed | 1 failed
代入演算子のオーバーロード
上のstructでは、代入演算子がオーバーロードされている。
その中では、on
関数が呼ばれている。
これ以上は追わないが、要は代入演算子がオーバーロードされていて、その中で
[] {
expect(sum(1, 2) == 3_i);
expect(sum(2, 3) == 6_i);
};
が実行されているということである。
まとめると、最初「ただの代入」と思っていたものは、ユーザー定義リテラルと代入演算子のオーバーロードによって「ただの代入」以上の処理になっていたのである。
おわりに
ユーザー定義リテラルと代入演算子のオーバーロードの組み合わせには夢がありますね(?)
用法用量を守って楽しく演算子オーバーロードしましょう。
Discussion