一度きりしか呼べないファンクターを管理したい

プログラミングC/C++

move-onlyな関数を扱えるstd::functionのようなものを実装するの派生。

一度しか呼べないことが保証されたstd::functionのようなものが欲しい。ファンクターをコールできるのは一度きりで、呼んだ後は必ずデストラクトされるようにしたい。ついでに、std::functionでは保持できないmove-onlyなファンクターも保持したい。

本ページのサンプルコードは以下の場所にある。
onetime_function.hpp

以前に作った(このページ冒頭の記事)komori::unique_functionとの主な差分箇所を抜粋する。

    template <std::nullptr_t Dummy = nullptr>
    auto operator()(ArgTypes&&... args)
    -> std::enable_if_t<!std::is_same<Res, void>::value && Dummy == nullptr, Res> {
      if (storage_) {
        auto&& res = invoker_(storage_, std::forward<ArgTypes>(args)...);
        onetime_function().swap(*this);
        return std::forward<Res>(res);
      } else {
        throw std::runtime_error("storage is null");
      }
    }

    template <std::nullptr_t Dummy = nullptr>
    auto operator()(ArgTypes&&... args)
    -> std::enable_if_t<std::is_same<Res, void>::value && Dummy == nullptr, Res> {
      if (storage_) {
        invoker_(storage_, std::forward<ArgTypes>(args)...);
        onetime_function().swap(*this);
      } else {
        throw std::runtime_error("storage is null");
      }
    }

戻り値(Res)がvoidかどうかによって実装を切り替えている1。戻り値がvoid以外の場合、invokeの戻り値を中継して呼び出し元に返却する必要があるが、voidの場合は必要ない。C++ではvoid型の変数を宣言することは許されないので、SFINAEを用いて実装を切り替えている。

やっていることは単純で、関数呼び出し直後にnullファンクターとswapするだけである。入れ替え先のnullファンクターはswap直後にデストラクトされるので、storageで抱えているファンクターを2度以上呼び出すことはできない。

storageがnullの場合の処理はやや迷ったが、ここではruntime errorを上げるようにしている。

(2021/02/15追記)https://github.com/komori-n/unique-function にて公開。

notes

  1. member関数のSFINAEによる実体化抑制はSFINAEでtemplate classのメンバ関数の実体化を制御するを参照。

プログラミングC/C++

Posted by komori