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);
}

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

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();
}

列挙型が特定の列挙子をもっているか判定する

ひさしぶりにテンプレートメタプログラミングをしました。

候補となる列挙型がだいたい同じ列挙子をもっているけど、一部の列挙型にだけある列挙子が存在する場合もある、という場合に処理を集約させたい場合のコードです。

#define DEFINE_HAS_ENUMERATOR(name) \
struct has_enumerator_##name { \
    template <class T> \
    static constexpr decltype(T::name, bool{}) \
        call(T) { return true; } \
    static constexpr bool \
        call(...) { return false; } \
}

#define HAS_ENUMERATOR(type, name) has_enumerator_##name ::call(type{})

DEFINE_HAS_ENUMERATOR(c);

#include <iostream>
#include <string>
#include <stdexcept>

template <class T>
std::string f(T x) {
    // 特定の列挙型にしかない列挙子
    if constexpr (HAS_ENUMERATOR(T, c)) {
        if (x == T::c) {
            return "c";
        }
    }

    // 共通
    switch (x) {
        case T::a:
            return "a";
        case T::b:
            return "b";
        default:
            throw std::invalid_argument("invalid enumerator");
    }
}

enum class A { a, b };
enum class B { a, b, c };

int main() {
    std::cout << f(A::a) << std::endl;
    std::cout << f(B::c) << std::endl;
}

出力:

a
c

if constexprはべんりですね。

マクロはなくせたらよかったのですが、関数ローカルで関数テンプレートをもつ関数オブジェクトが定義できなかったので泣く泣くこうなっています。std::variantでよく使われるoverload (こういうの) をうまく使えばできるかもしれませんが、今回はそこまではやってません。

Boost 1.74.0リリース

Boost 1.74.0がリリースされました。リリースノートはいつものように、boostjpサイトで翻訳 + 情報補完したものを公開しています。

新ライブラリは、STLInterfaces。CRTPベースでコンテナのビュー、イテレータ、シーケンスコンテナを手軽に書けるようにしたライブラリで、Boost.Iteratorの新バージョンと考えられます (Boost.Iterator作者のDave AbrahamsはBoostから離れてしまったので新規書き直し)。

Boost 1.73.0リリース

Boost 1.73.0がリリースされました。リリースノートはいつものように、boostjpサイトで翻訳 + 情報補完したものを公開しています。

新ライブラリは、WindowsUTF-8出力できるcoutとかが含まれるNowideライブラリ、コンパイル時文字列ライブラリStaticStringの2つです。

今回から、C++03サポートを非推奨化する動きがでてきました。将来的に、サポートされる下限のC++言語バージョンがC++14になりそうです。 世の中のC++解説も、C++14くらいがベースになっていい頃合いなのかもしれません。

Chronoライブラリ最後の日

遊びで調べただけのネタ記事です。

C++20段階の標準時間ライブラリChronoが、西暦何年まで扱えるのかを調べました。

#include <iostream>
#include <chrono>

using namespace std::chrono;

int main() {
    auto tp = system_clock::time_point::max();
    // ほんとはこれでできるはず (できない悲しい)
    // std::cout << tp << std::endl;

    auto dp = floor<days>(tp);
    year_month_day date{dp}; // 日付だけでなく時間もとりたかったけど、hh_mm_ssの実装がない

    // 日付を見てみる
    // ほんとはこう書けるけど、実装がない
    // std::cout << date << std::endl;
    std::cout
        << static_cast<int>(date.year()) << '/' // yearクラスが16ビット符号付き整数で年を管理しているので溢れている
        << static_cast<unsigned int>(date.month()) << '/'
        << static_cast<unsigned int>(date.day())
        << std::endl;

    // 年だけ考えたらどうなるか
    std::cout << floor<years>(system_clock::duration::max()).count() + 1970 << std::endl;

    // time_tでローカル時間を見てみる
    std::time_t t = system_clock::to_time_t(tp);
    std::cout << std::ctime(&t) << std::endl;
}

出力:

32103/1/10
294247
Sun Jan 10 13:00:54 294247

yearクラスの問題がなければ西暦でだいたい約30万年くらいまで扱えるようです。

もしこれが64ビットの符号なし整数で、西暦1970年1月1日から秒単位で数えていたら、西暦6000億年くらいまで扱えます。いまのChronoライブラリのlibc++の実装では、マイクロ秒単位で数えています。また仕様として、符号付き整数型として時間間隔を扱っているのでこうなっています。

符号付きで半分になるので3000億年、マイクロ秒を扱うので1/100万。これでだいたい30万年になります。

結論

C++20段階のlibc++実装のChronoライブラリを使う場合は、だいたい西暦30万年くらいまでにほかの実装に移行してください。

考えられるライブラリ設計の改善点

西暦30万年を超えて生きたい場合は、ライブラリの設計を改善する必要があります。そのために考えられる設計の改善点は、以下のようなことです:

  • system_clock::now()に時間単位を指定できるようにし、秒単位でカウントする。いまは実装定義の時間単位で返されるのでlibc++はマイクロ秒を返している
  • time_pointは時間間隔のdurationと違って1970年より前の日時を扱える必要はないので、符号なしのdurationを扱えるようにする

これで西暦6000億年くらいまで、このライブラリを使えるようになります。

C++ MIX #7 開催案内

cppmix.connpass.com

次回のC++ MIXの日程が決まりました。2020年1月29日 (水) です。

前回、コミュニティの成熟度が上がったと考え、定員を50人ベースから60人ベースに増やしました。

ディスカッションも最初に実施したときの問題をできるだけ改善し、なるべく小さい単位でテーブルを分割して声が届きやすいよう調整しました。今回もまた、当日に柔軟に対応して人数調整や、テーブル移動を促したりしていこうと思います。