static_assertの比較値をエラーメッセージに表示する

C++static_assert(A == B);は表明失敗時にfalseということしかわからない場合があります。

GCC 12以上、Clang 16以上であれば、比較値をエラーメッセージに表示してくれます。

int main()
{
    constexpr int a = 1;
    constexpr int b = 2;
    static_assert(a == b);
}

GCC :

prog.cc: In function 'int main()':
prog.cc:5:21: error: static assertion failed
    5 |     static_assert(a == b);
      |                   ~~^~~~
prog.cc:5:21: note: the comparison reduces to '(1 == 2)'

Clang :

prog.cc:5:5: error: static assertion failed due to requirement 'a == b'
    static_assert(a == b);
    ^             ~~~~~~
prog.cc:5:21: note: expression evaluates to '1 == 2'
    static_assert(a == b);
                  ~~^~~~
1 error generated.

それより古いコンパイラの場合は、クラスの非型テンプレートパラメータとして左辺、右辺の値をとり、そのなかでstatic_assertすることで、エラーメッセージにテンプレートパラメータを含むクラス名が表示されるので比較値がわかるようにできます。

template <auto A, auto B>
struct static_assert_eq {
    static constexpr bool value = true;
    static_assert(A == B);
};

int main()
{
    constexpr int a = 1;
    constexpr int b = 2;
    static_assert(static_assert_eq<a, b>::value);
}

メンバ変数宣言の文脈で使うような場合に、このような2段階のstatic_assertが必要になります。

struct X {
    static constexpr int a = 1;
    static constexpr int b = 2;
    static_assert(static_assert_eq<a, b>::value); // 文脈的にstatic_assert_eq<a, b>{};はできない
};

C++日本語リファレンスcpprefjpでスポンサー募集をはじめました

cpprefjp.github.io

opencollective.com

C++日本語リファレンスを提供しているcpprefjpプロジェクトで、スポンサー募集をはじめました。

スポンサー募集のサービスとしては、Open Collectiveを使用しています。このサービスの特徴としては、手数料として10%ほど引かれますが、一般社団法人などを作らずとも団体口座をもつことができ、その口座の状況がオープンになるのでオープンソースのプロジェクトで使いやすいサービスとなっています。

私たちのWebサイトは広告をつけていないので、これまでは完全に無償で編集活動を行ってきました。しかしそれでは継続性に問題があるので、編集者に報酬を支払うことで継続的にC++の最新情報をみなさんに提供できるようにしていきます。

追記

スポンサーになっていただいた方はロゴ画像とリンクをcpprefjpのトップページに記載させていただきます。

スポンサーになっていただいた方のOpen Collectiveページに連絡先を記載いただければロゴ画像とリンクについてこちらからご連絡させていただきますが、そうでなければOpen Collectiveの問い合わせフォームからご連絡いただくようよろしくお願いします。

C++ / Boostの新たな非営利団体The C++ Alliance

Boost C++ Librariesのコンサルティング会社BoostProがなくなって以来、Boostはリーダー不在でふわふわした状態でした。

Boost.Beast作者のVinnie Falco氏が創設した新しい非営利団体The C++ Allianceでは、Boostを復権させるようBoostの基礎部分を刷新し、C++全般を支援していくことを主な目的としています。

この団体では、Boost.Asio作者のChristopher Kohlhoff氏を雇用し、フルタイムでオープンソース活動をできるようにしています。ほかにもスタッフエンジニアとして、Boostのリリースマネージャを長く続けているMarshall Clowも雇用しています。

C++23記事の訂正 : enumerateの使い方が間違っていました

2023年2月17日発売の『Software Design 2023年3月号』に書いたC++23記事で、以下のようにviews::enumerateの例を紹介していますが、

for (auto x : enumerate(v)) {
    println("{} {}", x.index, x.value);
}

xの型は実際にはtuple<difference_type, T>となる予定なので、index/valueメンバ変数はもっていませんでした。このコードは間違いでした。

基本的に構造化束縛で使うことになりそうです。

for (auto [index, value] : enumerate(v)) {
    println("{} {}", index, value);
}

『Software Design』誌にC++23の解説記事を書きました

gihyo.jp

2023年2月17日発売の『Software Design 2023年3月号』に、C++23の解説記事を書きました!

C++11からC++20までの進化を振り返りつつ、2023年末に策定されるC++23がどのように変わるのかを解説しています。

私個人へのスポンサー募集を開始しました

github.com

GitHub Sponsorsで、私個人へのスポンサー募集を開始しました。

私はcpprefjpとboostjpという2つのウェブサイトを運営・執筆しています。

cpprefjpはC++の日本語リファレンスサイトで、boostjpはBoost C++ Librariesの日本語情報サイトです。これらのウェブサイトはもう10年以上続いていて、いまでは膨大な情報を提供しています。

しかし活動はすべてボランティアなので、私のやる気に依存しすぎているところがあります。プライベートが忙しいときは半年以上、作業がストップしてしまったこともありますし、やる気がでないから作業が止まり情報が古くなってしまう、ということが起きてしまう危険があります。

なので、経済的に応援していただくことで、これらの活動をより持続的なものにしたいと思い、スポンサーを募集させていただくことにしました。

「無報酬だからやる気に応じて作業すればいい」という状態から、「報酬があるから責任をもって作業する」という状態にしていきたいです。

よろしくお願いします!

optionalとshared_ptrで共通のnull

std::optionalstd::shared_ptrを両方使っていると、空状態を設定するためにあっちはstd::nullopt、こっちはnullptrのように指定するので、使い分けがわずらわしいときがあります。

なにも考えず{}を指定してもよいのですが、「値初期化というのではなく空状態を指定することを示したい!」という欲求があるので、std::optionalstd::shared_ptr両方の空状態に変換できるオブジェクトがあると便利かなと思ったりします。

#include <optional>
#include <memory>

struct unified_null {
    template <class T>
    operator std::shared_ptr<T>() const { return {}; }

    template <class T>
    operator std::optional<T>() const { return {}; }
};

inline constexpr unified_null unull{};

int main()
{
    std::optional<int> o = unull;
    std::shared_ptr<int> p = unull;
}

・・・どうだろう?

jthreadを簡易実装する

C++20で導入されたstd::jthreadは、スレッドを中断させる仕組みをもった便利なクラスです。今回は、C++14でそれを簡易的に実装してみました (C++14の機能としてはラムダ式の初期化キャプチャを使っています。ラムダ式に外の変数をムーブするために必要)。

std::thread + std::promisestd::futureを使えばできます。

std::promiseに値を設定することで中断リクエストし、スレッド側のstd::futurestd::promiseに値が設定されるまで (中断リクエストされるまで) 処理を続けます。

std::promiseには1回しか値を設定できないので (2回設定すると例外が投げられる)、2回以上設定されないようにする必要があります。

#include <thread>
#include <future>
#include <utility>

class join_thread {
    bool _stop_requested = false;
    std::promise<void> _stop_request;
    std::thread _thread;
public:
    template <class F>
    explicit join_thread(F f)
    {
        _thread = std::thread {
            [g = std::move(f), fut = _stop_request.get_future()] mutable {
                g(std::move(fut));
            }
        };
    }

    ~join_thread()
    {
        join();
    }

    void request_stop()
    {
        if (!_stop_requested) {
            _stop_requested = true;
            _stop_request.set_value();
        }
    }

    void join()
    {
        request_stop();
        if (_thread.joinable()) {
            _thread.join();
        }
    }
};

#include <iostream>
#include <chrono>
void f(std::future<void> stop_request)
{
    int sum = 0;
    // 0秒waitでfutureに値が設定されたかを確認
    while (stop_request.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
        ++sum;
    }
    std::cout << sum << std::endl;
}

int main()
{
    join_thread t{f};

    std::this_thread::sleep_for(std::chrono::milliseconds(3));

    t.request_stop();
    t.join();
}

出力例:

32244

あとがき:

std::promisestd::futureの代わりに、std::atomic<bool>をスレッド間で共有するとかでもよかったですね…そちらの方がコストが安そう。実際はどちらでもよいですが、std::threadを使ったスレッドに、いろいろな方法で外から中断リクエストを送れる、ということでした。

std::atomic<bool>を使ったバージョンも載せておきます。

#include <thread>
#include <atomic>
#include <utility>

class join_thread {
    std::atomic<bool> _stop_request{false};
    std::thread _thread;
public:
    template <class F>
    explicit join_thread(F f)
    {
        _thread = std::thread{
            [this, g = std::move(f)] mutable {
                g(_stop_request);
            }
        };
    }

    ~join_thread()
    {
        join();
    }

    void request_stop()
    {
        _stop_request.store(true);
    }

    void join()
    {
        request_stop();
        if (_thread.joinable()) {
            _thread.join();
        }
    }
};

#include <iostream>
#include <chrono>
void f(std::atomic<bool>& stop_request)
{
    int sum = 0;
    while (!stop_request.load()) {
        ++sum;
    }
    std::cout << sum << std::endl;
}

int main()
{
    join_thread t{f};

    std::this_thread::sleep_for(std::chrono::milliseconds(3));

    t.request_stop();
    t.join();
}