boost-ext/utに見るユーザー定義リテラルと代入演算子のオーバーロードの使い方


はじめに

boost-ext/ut(以下UT)は、C++の軽量な単体テストフレームワークである。

https://github.com/boost-ext/ut

シングルヘッダーライブラリで、マクロフリー、コンパイル時間が短いといったことが売りである。
例えば、次のように使える。

#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とは何だろうか。これはユーザー定義リテラルと呼ばれるものである。

https://cpprefjp.github.io/lang/cpp11/user_defined_literals.html

詳しくは上記記事に譲るが、例えば"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}};
}

https://github.com/boost-ext/ut/blob/3a6c09ca3b073540387711c3261794dba278436b/include/boost/ut.hpp#L1586-L1589

ここでdetail::testはstructである。

https://github.com/boost-ext/ut/blob/3a6c09ca3b073540387711c3261794dba278436b/include/boost/ut.hpp#L1409-L1450

ちなみに最初の方に書いた3_i6_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関数が呼ばれている。

https://github.com/boost-ext/ut/blob/3a6c09ca3b073540387711c3261794dba278436b/include/boost/ut.hpp#L1391-L1395

これ以上は追わないが、要は代入演算子がオーバーロードされていて、その中で

[] {
        expect(sum(1, 2) == 3_i);
        expect(sum(2, 3) == 6_i);
    };

が実行されているということである。

まとめると、最初「ただの代入」と思っていたものは、ユーザー定義リテラルと代入演算子のオーバーロードによって「ただの代入」以上の処理になっていたのである。

おわりに

ユーザー定義リテラルと代入演算子のオーバーロードの組み合わせには夢がありますね(?)
用法用量を守って楽しく演算子オーバーロードしましょう。