💡 要約:std::functionの内部動作(型消去、小さなバッファ最適化)と使用例、そしてC++23のstd::move_only_functionがムーブのみの呼び出し可能オブジェクトを格納するためにstd::functionの代わりになる場面。
最終更新:2026-10-24
C++ std::function 詳解¶
最近C++のコードを書いていてis_functionを使う機会があり、ついでにソースコードの実装を見ていたところ、いくつか問題を発見しました。以前私たちが学んだのは3つの点...ですが、6つの点......が何なのかご存知ですか?
同様に、次のようなコードがあった場合:
これが何を意味するかおわかりでしょうか?
以下、一緒に詳細を深く見ていきましょう!
1.6つの点¶
6つの点は通常 two ellipsis operators と呼ばれます。6つの点を分割すると、次の3つの場合が得られます
template <typename... Args> void foo1(Args......)
template <typename... Args> void foo2(Args... ...)
template <typename... Args> void foo3(Args..., ...)
これで見やすくなりました。std::is_functionのコードを見てみましょう。
2.is_functionの実装詳細¶
std::is_functionの実装には、プライマリテンプレートと2つの部分特殊化が関わっています:
// 主模板,假设提供的类型不是函数类型
template<typename T>
struct is_function : std::false_type {};
// 偏特化,用于正常的函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args...)> : std::true_type {};
// 偏特化,用于变参函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args......)> : std::true_type {};
- プライマリテンプレートは、与えられた型が関数型ではないと仮定し、
std::false_typeを結果として返します。 - 1番目の部分特殊化は通常の関数型のためのもので、
Retは戻り値の型、Args...は引数の型を表します。与えられた型がこの関数シグネチャに一致する場合、std::true_typeが結果となります。 - 2番目の部分特殊化は可変引数関数型のためのもので、その省略記号は関数が可変個の引数を持つことを表します。同様に、与えられた型がこの関数シグネチャに一致する場合、
std::true_typeが結果となります。
3.使用例¶
最後にstd::is_functionの使用例を示します:
#include <iostream>
#include <type_traits>
int func(int x, int y) { return x + y; }
int vararg_func(int x, ...) { return x; }
int main() {
std::cout << std::boolalpha;
std::cout << "Is func a function? " << std::is_function<decltype(func)>::value << std::endl;
std::cout << "Is vararg_func a function? " << std::is_function<decltype(vararg_func)>::value << std::endl;
return 0;
}
これが出力されます:
理解できましたか、この節は以上です~
C++23 std::move_only_function vs std::function¶
先ほどの型消去には隠れた代償があります。std::functionはラップした呼び出し可能オブジェクトがコピー構築可能であることを要求します。ラムダでstd::unique_ptrをキャプチャして格納しようとすると、コンパイラは拒否します:
auto make_sink() {
auto ptr = std::make_unique<std::vector<int>>(42);
return [ptr = std::move(ptr)] { return ptr->size(); }; // move-only closure
}
// std::function<std::size_t()> f = make_sink(); // ERROR: closure not copyable
std::move_only_function<std::size_t()> g = make_sink(); // OK since C++23
std::move_only_function(C++23、ヘッダ
選び方:呼び出し可能オブジェクトのコピーが安くレガシーAPIとの互換性が必要ならstd::functionのまま。クロージャが何かを排他的に所有するならstd::move_only_functionへ。またstd::functionは代入時にクロージャをコピーする点にも注意——重いキャプチャでは実費が発生し、move_only_functionはそれを避けられます。性能がシビアな呼び出し点では、素のテンプレートパラメータが両者より上です:消去なし、間接なし、完全インライン化可能。
よくある質問¶
std::functionとstd::move_only_functionの違いは?¶
std::functionはコピー可能な呼び出し可能オブジェクトを要求し、代入時にコピーします。std::move_only_function(C++23)はムーブのみの呼び出し可能オブジェクトを受け入れ、ムーブだけを行います。それ以外の動作と呼び出し構文は同じです。
なぜstd::functionはunique_ptrをキャプチャしたラムダを格納できないのですか?¶
std::functionは格納した呼び出し可能オブジェクトをコピーできなければならず、unique_ptrのようなムーブのみの型をキャプチャした時点でクロージャのコピーコンストラクタは削除されます。std::move_only_functionかテンプレートパラメータを使ってください。
std::move_only_functionはstd::functionより速いですか?¶
コピーのオーバーヘッドと一部の簿記を省けますが、どちらも型消去と間接呼び出しを通ります。呼び出し点で型が分かるなら、呼び出し可能オブジェクトをテンプレートパラメータにするのが本当の高速 pathです。
関連トピック¶
- C++ 純粋仮想関数と抽象クラス | C++那些事
- C++ assert アサーション | C++那些事
- C++ ビットフィールド | C++那些事
- C 言語による C++ 多態の実現 | C++那些事
- C++ const 詳解:定数・const ポインタとポインタ const の違い | C++那些事
- C++ const ファミリー | C++那些事
- C++ 間接参照演算子 * | C++那些事
- C++ 初期化と代入 | C++那些事
- C++ 複数戻り値の書き方 | C++那些事
- C++ mutable キーワード | C++那些事