std::functionと愉快な仲間たち ～move_only_function, copyable_function, function_ref～

# はじめに
C++標準ライブラリでは「関数のように呼び出し可能なエンティティ」を統一的に取り扱える [`std::function<R(ArgTypes...)>`](https://cpprefjp.github.io/reference/functional/function.html) クラステンプレートが提供されます。

`std::function` の導入によって便利になった一方で、クラス仕様に関する問題点が複数指摘されており、C++23および次期C++26標準ライブラリでは `std::function` の代替となる3種類のクラステンプレートが追加されました。

- [`std::move_only_function`](https://cpprefjp.github.io/reference/functional/move_only_function.html) ［C++23］
- [`std::copyable_function`](https://cpprefjp.github.io/reference/functional/copyable_function.html)［C++26］
- [`std::function_ref`](https://cpprefjp.github.io/reference/functional/function_ref.html)［C++26］

本記事ではこれらクラステンプレート群について、それぞれの特徴や使いどころを簡単に紹介します。

:::note info
目的別に機能特化した3種類クラステンプレートの登場により `std::function`は [“いらない子”](https://x.com/yohhoy/status/1670636068566351872) になってしまうのですが、C++26時点で [`std::function`を非推奨化(deprecated)する提案](https://github.com/cplusplus/papers/issues/1745) はさすがに却下されました。C++26準拠環境が広く普及するまでは時間を要するでしょうから、当面の間は `std::function` を継続利用しても問題ないと思います。
:::


## std::function［C++11］
[`std::function`](https://cpprefjp.github.io/reference/functional/function.html)クラステンプレートは、下記のエンティティを格納できます。項番1～4は同一の関数シグニチャ`int(int)`[^funcsig]となり、利用側では実際に何が格納されているかを知らなくても関数のように呼び出せます。項番5のメンバ関数では関数シグニチャ`int(MyClass&, int)`のようにクラス名が追加されますが、[`std::bind`](https://cpprefjp.github.io/reference/functional/bind.html)や[`std::bind_front`](https://cpprefjp.github.io/reference/functional/bind_front.html)と組み合わせて第1引数のオブジェクトを固定(bind)できます。

1. 通常の関数（関数ポインタ）
2. ラムダ式[^lambda]
3. `operator()`を実装したクラス（関数オブジェクト）
4. クラスの静的メンバ関数[^static-memfunc]
5. クラスのメンバ関数
6. クラスのメンバ変数[^datamem]

[^funcsig]: 関数シグニチャ(signature)に馴染みがなければ、「通常の関数宣言から関数名を削除したもの」と解釈すれば分かりやすいと思います。例えば関数`int func(int)`は関数型`int(int)`を持ちます。
[^lambda]: C++の [ラムダ式(lambda expression)](https://cpprefjp.github.io/lang/cpp11/lambda_expressions.html) は「`operator()`を実装した匿名のクラス(closure type)」のインスタンスを生成します。これは、本質的には項番3の関数オブジェクトと同義です。
[^static-memfunc]: 静的メンバ関数(static member function)の関数シグニチャは通常の関数シグニチャと同じですから、本質的には項番1の関数ポインタと同義です。
[^datamem]: おそらくほとんど知られていないマイナー機能と思われますが、C++標準ライブラリの`std::function`は [メンバ変数へのポインタ](https://yohhoy.hatenadiary.jp/entry/20120307/p1) の取り扱いもサポートします。この奇妙な仕様は [*`INVOKE`* 仮想操作](https://cpprefjp.github.io/reference/concepts/Invoke.html) を介して実現されています。

```cpp:(std::function利用例)
#include <functional>
using namespace std;

int func(int);

struct Callable {
  int operator()(int);
};

struct MyClass {
  int member;
  int mem_func(int);
  static int static_mem_func(int);
};

int main()
{
  // 通常の関数
  function<int(int)> fn1 = &func;
  fn1(42);
  // ラムダ式
  function<int(int)> fn2 = [](int n) -> int { return n; };
  fn2(42);
  // operator()を実装したクラス
  Callable callable;
  function<int(int)> fn3 = callable;
  fn3(42);
  // クラスの静的メンバ関数
  function<int(int)> fn4 = &MyClass::static_mem_func;
  fn4(42);

  // クラスのメンバ関数
  MyClass obj;
  function<int(MyClass&, int)> fn5a = &MyClass::mem_func;
  function<int(int)> fn5b
    = bind(&MyClass::mem_func, obj, std::placeholders::_1);
  // C++20以降なら bind_front(&MyClass::mem_func, obj) と書ける
  fn5a(obj, 42);
  fn5b(42);

  // クラスのメンバ変数
  function<int&(MyClass&)> fn6 = &MyClass::member;
  fn6(obj) = 42;
}
```


## std::move_only_function［C++23］
C++23標準ライブラリに追加された[`std::move_only_function`](https://cpprefjp.github.io/reference/functional/move_only_function.html)クラステンプレートは、`std::function`が抱える下記の問題点を解決します。

1. コピー不可／ムーブのみ可能なエンティティを扱えない
2. 呼び出し時に毎回nullチェックが行われる
3. const性が正しく伝搬されない
4. noexcept性が正しく伝搬されない
5. 右辺値／左辺値性が正しく伝搬されない
6. 実行時型情報(RTTI)サポートを要求する[^no-rtti]

項番1は名前に "move_only" と含まれる通り、`move_only_function`ではコピー不可／ムーブのみ可能なエンティティを取り扱えます。当然ながら、`move_only_function`自身もコピー不可／ムーブのみ可能なオブジェクトとなります。

項番2は動作性能の違いをもたらします。`std::function`は[呼び出し](https://cpprefjp.github.io/reference/functional/function/op_call.html)のたびにエンティティ保持有無をチェックします。`move_only_function`では[事前確認](https://cpprefjp.github.io/reference/functional/move_only_function/op_bool.html)しておくことで呼び出し処理のオーバーヘッドを削減します。

項番3～5のうち「const性の伝搬」についてのみ例示コードで説明します。これ以外のユースケースは [cpprefjpの例](https://cpprefjp.github.io/reference/functional/move_only_function.html) を参照ください。関数シグニチャ`string()`と`string() const`に対して、`move_only_function`オブジェクト自身のconst性が適用されていることに着目してください。

- `mof1`: (非const)オブジェクト経由で(非const)メンバ関数を呼び出す
- `mof2`: constオブジェクト経由で(非const)メンバ関数は呼び出せない（コンパイルエラー）
- `mof3`: (非const)オブジェクト経由でconstメンバ関数を呼び出す
- `mof4`: constオブジェクト経由でconstメンバ関数を呼び出す

一方で`std::function`ではオブジェクト自身のconst性によらず、常に(非const)メンバ関数が呼び出されます。

[^no-rtti]: 実行時型情報(RTTI)サポートの要求は、字義通りのため本文中での説明は割愛します。`std::function`の[`target_type()`](https://cpprefjp.github.io/reference/functional/function/target_type.html)や[`target()`](https://cpprefjp.github.io/reference/functional/function/target.html)なんて利用してる人いないでしょ？

```cpp:(const性伝搬の例)
#include <functional>
#include <print>
using namespace std;

struct Functor {
  // (非const)メンバ関数
  string operator()()
    { return "non-const"; }
  // constメンバ関数
  string operator()() const
    { return "const"; }
};

int main()
{
        move_only_function<string()>       mof1 = Functor{};
  const move_only_function<string()>       mof2 = Functor{}; // (利用時にコンパイルエラー)
        move_only_function<string() const> mof3 = Functor{};
  const move_only_function<string() const> mof4 = Functor{};
  println("mof1: {}", mof1()); // "non-const"
//println("mof2: {}", mof2()); // ❌コンパイルエラー
  println("mof3: {}", mof3()); // "const"
  println("mof4: {}", mof4()); // "const"

        function<string()>       fn1 = Functor{};
  const function<string()>       fn2 = Functor{};
//      function<string() const> fn3 = Functor{}; // ❌コンパイルエラー
//const function<string() const> fn4 = Functor{}; // ❌コンパイルエラー
  println("fn1: {}", fn1()); // "non-const"
  println("fn2: {}", fn2()); // "non-const"
}
```


## std::copyable_function［C++26］
C++26標準ライブラリに追加される[`std::copyable_function`](https://cpprefjp.github.io/reference/functional/copyable_function.html)クラステンプレートは、その名前の通り `std::move_only_function` の「コピー対応バージョン」です。

`std::function`が抱える下記の問題点を解決します。

1. 呼び出し時に毎回nullチェックが行われる
2. const性が正しく伝搬されない
3. noexcept性が正しく伝搬されない
4. 右辺値／左辺値性が正しく伝搬されない
5. 実行時型情報(RTTI)サポートを要求する

`std::move_only_function`および`std::copyable_function`が解決する「const性の伝搬」は、`std::function`の仕様バグとして[2014年頃から指摘](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4159.pdf)されていました。C++26以降は`std::function`の機能改善された代替品として`std::copyable_function`を利用できます。


## std::function_ref［C++26］

C++26標準ライブラリに追加される[`std::function_ref`](https://cpprefjp.github.io/reference/functional/function_ref.html)クラステンプレートは、対象エンティティを所有管理しない軽量ラッパーです。関数引数などのAPI仕様において、シグニチャ型を固定したいユースケースが想定されます。

高階関数の引数として関数オブジェクトや関数ポインタを汎用的に受けとる場合、C++23現在は関数テンプレートとして実装するか(`hof1`)、オーバーヘッドの大きい`std::function<R(Ts...)>`引数型を利用する(`hof2`)しか選択肢がありませんでした。C++26以降では`std::function_ref<R(Ts...)>`を引数型に利用することで、これらの問題が解決します(`hof3`)。

```cpp
#include <functional>
using namespace std;

// 関数テンプレートとして高階関数を実装
// ✅利点: 実行時オーバーヘッドが小さい
// ❌欠点: 関数シグニチャが型検査されない（ドキュメント必須）
// ❌欠点: 大量にインスタンス化されると生成コードが肥大化
template <class F>
void hof1(F f);

// 引数型にstd::functionを利用
// ✅利点: 関数シグニチャが型検査される
// ❌欠点: 実行時オーバーヘッドが大きい
void hof2(function<int(int)> f);

// 引数型にstd::function_refを利用
// ✅利点: 関数シグニチャが型検査される
// ✅利点: 実行時オーバーヘッドが小さい
void hof3(function_ref<int(int)> f);
```

目的特化された`std::function_ref`は、以下の特徴をもちます。

1. オブジェクトサイズが小さい[^smallobj]
2. 対象エンティティの設定が必須（呼び出し時nullチェックなし）
3. メンバ関数・メンバ変数を直接サポートする（`bind`等を用いない） 
4. const性・noexcept性を伝搬する[^lvalue-call]
5. 実行時型情報(RTTI)サポート不要

[^smallobj]: `std::function_ref`オブジェクトは、ポインタ2個分のサイズで実装可能とされています。高階関数の引数型として利用する場合、このオブジェクトサイズだとスタック渡しではなくレジスタ渡しによる軽量な取り扱いが可能となります。技術詳細は提案文書[P0792R14](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0792r14.html)を参照ください。
[^lvalue-call]: `std::function_ref`は`std::move_only_function`や`std::copyable_function`と異なり右辺値／左辺値性を伝搬せず、対象エンティティが左辺値として呼び出し可能であること(Lvalue-callable)要求します。この動作に関しては`std::function`と同じです。

`function_ref`におけるメンバ関数の直接サポートは、従来方式とは異なるアプローチで実現されます。コンストラクタの第1引数に非型タグ[`nontype<F>`](https://cpprefjp.github.io/reference/utility/nontype_t.html)でメンバ関数ポインタを、第2引数には対象オブジェクトを指定することで、メンバ関数にthisポインタを束縛した関数シグニチャ`int(int)`として取り扱えます。

```cpp:(メンバ関数サポート)
#include <functional>
using namespace std;

int f(int);

struct MyClass {
  int mem_func(int);
};

int main()
{
  // 通常の関数
  function_ref<int(int)> fn1 = f;
  fn1(42);

  // クラスのメンバ関数
  MyClass obj;
  function_ref<int(MyClass&, int)> fn2a = nontype<&MyClass::mem_func>;
  fn2a(obj, 42);

  function_ref<int(int)> fn2b{nontype<&MyClass::mem_func>, obj};
  fn2b(42);
}
```


# まとめ
最後にC++標準ライブラリ`<functional>`が提供する`std::function`ファミリの特徴を表形式で整理します。C++26が待ち遠しいですね（毎年同じような結び...）

|特徴|[function](https://cpprefjp.github.io/reference/functional/function.html)|[move_only<br>_function](https://cpprefjp.github.io/reference/functional/move_only_function.html)|[copyable<br>_function](https://cpprefjp.github.io/reference/functional/copyable_function.html)|[function_ref](https://cpprefjp.github.io/reference/functional/function_ref.html)|
|:--|:-:|:-:|:-:|:-:|
|ムーブのみ型<br>サポート| ❌ | ✅ | ❌<br>(設計判断) | ✅ |
|所有権管理| ✅ | ✅ | ✅ | － |
|const伝搬<br>noexcept伝搬| ❌ | ✅ | ✅ | ✅ |
|右/左辺値伝搬| ❌ | ✅ | ✅ | ❌<br>(設計判断) |
|メンバ関数<br>サポート| ✔️<br>(要bind) | ✔️<br>(要bind) | ✔️<br>(要bind) | ✅ |
|呼び出し時<br>nullチェック| ❌(有) | ✅(無) | ✅(無) | ✅(無) | 
|RTTI要求| ❌(有) | ✅(無) | ✅(無) | ✅(無) | 
|推論補助| [✅](https://cpprefjp.github.io/reference/functional/function/op_deduction_guide.html)<br>([一部留保](https://yohhoy.hatenadiary.jp/entry/20201109/p1)) | ❌<br>([留保](https://stackoverflow.com/questions/69509236/)) | ❌<br>(留保) | [✅](https://cpprefjp.github.io/reference/functional/function_ref/op_deduction_guide.html) |
|C++標準| C++11 | C++23 | C++26 | C++26 |