コンテンツにスキップ

💡 要約:std::functionの内部動作(型消去、小さなバッファ最適化)と使用例、そしてC++23のstd::move_only_functionがムーブのみの呼び出し可能オブジェクトを格納するためにstd::functionの代わりになる場面。

最終更新:2026-10-24

C++ std::function 詳解

最近C++のコードを書いていてis_functionを使う機会があり、ついでにソースコードの実装を見ていたところ、いくつか問題を発見しました。以前私たちが学んだのは3つの点...ですが、6つの点......が何なのかご存知ですか?

同様に、次のようなコードがあった場合:

template<typename... Args>
struct foo(Args......);

これが何を意味するかおわかりでしょうか?

以下、一緒に詳細を深く見ていきましょう!

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

これが出力されます:

Is func a function? true
Is vararg_func a function? true

理解できましたか、この節は以上です~

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、ヘッダ)はコピー要件を外します。ムーブのみのクロージャを格納できるため、パイプライン、非同期タスク、ファイルハンドルや接続のような排他的リソースを保持するコールバックに自然にfitします。

選び方:呼び出し可能オブジェクトのコピーが安くレガシー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です。

関連トピック

コメント