以下の記事の派生。
move-onlyな関数を扱えるstd::functionのようなものを実装する
·2071 文字
技術解説
C++
一度しか呼べないことが保証された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追記)以下でコードを公開。
komori-n/unique-function
a simple move-only std::function replacement
C++
0
0
member関数のSFINAEによる実体化抑制は以下を参照。
↩︎SFINAEでtemplate classのメンバ関数の実体化を制御する·1843 文字技術解説 C++ SFINAE STL