Tech Notes

std::functionに入れられない関数オブジェクトの話

std::functionは、指定の引数リスト・戻り値型を持つ任意の関数オブジェクトを格納できる……これは実は真ではない。

C++ではstd::functionに入れられない関数オブジェクトというものが存在する。

具体的にはコピー不可能な関数オブジェクトは格納できない。 std::functionは格納する関数オブジェクトにコピーコンストラクト可能であることを要請する。

例として、以下のようなラムダ式はもうアウトである。

auto hoge = std::make_unique<int>(10);

auto f = [a = std::move(hoge)](){}

std::function<void()> func = std::move(f);  // エラー!

unique_ptrはコピー不可の型である。それをムーブキャプチャさせたラムダ式は当然unique_ptrの所有権を持っており、これもまたコピー不可能になる。そのためコピー可能性を要求するstd::functionには格納できない。 これが例えばshared_ptrならコピー可能なのでエラーは起きなくなる。

なぜコピー可能性などが求められるのか。それはstd::function型がコピー可能だからだ。

std::function<void()> f = [](){};

std::function<void()> g = f;  // コピー

std::functionがコピー可能であるためには中身もコピー可能である必要がある。それがこの仕様の理由である。


ところで、この関数オブジェクトfはムーブなら可能なはずである。実際、以下のコードは普通に通る。

auto hoge = std::make_unique<int>(10);
auto f = [a = std::move(hoge)](){}  // fはコピー不可
auto g = std::move(f);  // ムーブなら可能

結局上記の問題はstd::functionという型がコピー可能であることを保証する仕様に起因している。ならば、使う側としてはムーブしかしないからコピー不可な関数オブジェクトも入れさせてほしいという話になってくる。そこで出てくるのがC++23のstd::move_only_functionである。

auto hoge = std::make_unique<int>(10);

auto f = [a = std::move(hoge)](){}

std::move_only_function<void()> func = std::move(f);  // 可能

std::move_only_functionはその名の通りムーブしかできない。その代わりに格納するオブジェクトにもムーブ可能性しか求めない。有用な場面は多いだろう。

そもそもstd::functionを用いるユースケースにおいてコピー可能性が求められることはそれほど多くないのではないかと思う。ただの変数よりもconst変数、const変数よりもconstexprという流れがあったように、C++23の時代になれば「特に理由が無いのならstd::functionよりもstd::move_only_function」みたいなことが起きるかもしれない?