std::functionを引数とする場合、その関数の型が実際に呼び出す関数の型と異なる場合の挙動

## ことの発端

関数型プログラミングをやっていると関数を引数として渡すケースが出てきます。
で、型変換をうまい具合に吸収してくれる `std::function` をよく使うのですが、その際に「std::functionの関数型の仮引数の型」と「実際の関数の仮引数の型」が異なる場合に、仮引数に渡る値が「参照」のまま転送されるのか、コピーなのかムーブなのか確認したいと思います。

## 検証

(1) `A` というクラスはコンストラクタやデストラクタでログを記録します。
(2) 関数を受ける関数の型を `fn_t` と `fn_cref_t` で定義します。

```cpp
using fn_t      = std::function<void(A)>;
using fn_cref_t = std::function<void(const A&)>;
```

(3) 関数を引数として受け、その関数を呼び出す `call` というテンプレート関数を定義します。`F` には `fn_t` か `fn_cref_t` が入ります。

```cpp
template <typename F> void call(F f, const A& a){
    f(a);
}
```

(4) 実際の関数は下記のようになります。

```cpp
auto fn1 = [](A){};
auto fn2 = [](const A&){};
auto fn3 = [](A&&){};

auto fn1a = [](auto){};
auto fn2a = [](const auto&){};
auto fn3a = [](auto&& x){ /* x.f(); */ };
```

### サンプルコード

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

struct A {
    A()             { cout << "A::ctor default" << endl; }
    A(const A&)     { cout << "A::ctor copy" << endl; }
    A(A&&)          { cout << "A::ctor move" << endl; }
    ~A()            { cout << "A::dtor" << endl; }
    void f()        { cout << "A::f" << endl; }
};

using fn_t      = std::function<void(A)>;
using fn_cref_t = std::function<void(const A&)>;

auto fn1 = [](A){};
auto fn2 = [](const A&){};
auto fn3 = [](A&&){};

auto fn1a = [](auto){};
auto fn2a = [](const auto&){};
auto fn3a = [](auto&& x){ /* x.f(); */ };

template <typename F> void call(F f, const A& a){
    f(a);
}

int main(void){
    A a;
    cout << "----" << endl;
    
    cout << "fn_t #1" << endl;
    call<fn_t>(fn1, a);
    cout << "fn_t #2" << endl;
    call<fn_t>(fn2, a);
    cout << "fn_t #3" << endl;
    call<fn_t>(fn3, a);

    cout << "fn_t #1a" << endl;
    call<fn_t>(fn1a, a);
    cout << "fn_t #2a" << endl;
    call<fn_t>(fn2a, a);
    cout << "fn_t #3a" << endl;
    call<fn_t>(fn3a, a);

    cout << "fn_cref_t #1" << endl;
    call<fn_cref_t>(fn1, a);
    cout << "fn_cref_t #2" << endl;
    call<fn_cref_t>(fn2, a);
    cout << "fn_cref_t #3" << endl;
    cout << "(compile error)" << endl;
    // call<fn_cref_t>(fn3, a);

    cout << "fn_cref_t #1a" << endl;
    call<fn_cref_t>(fn1a, a);
    cout << "fn_cref_t #2a" << endl;
    call<fn_cref_t>(fn2a, a);
    cout << "fn_cref_t #3a" << endl;
    call<fn_cref_t>(fn3a, a);

    cout << "----" << endl;
}
```

### 結果

```:結果
A::ctor default
----
fn_t #1
A::ctor copy
A::ctor move
A::dtor
A::dtor
fn_t #2
A::ctor copy
A::dtor
fn_t #3
A::ctor copy
A::dtor
fn_t #1a
A::ctor copy
A::ctor move
A::dtor
A::dtor
fn_t #2a
A::ctor copy
A::dtor
fn_t #3a
A::ctor copy
A::dtor
fn_cref_t #1
A::ctor copy
A::dtor
fn_cref_t #2
fn_cref_t #3
(compile error)
fn_cref_t #1a
A::ctor copy
A::dtor
fn_cref_t #2a
fn_cref_t #3a
----
A::dtor
```

## まとめ

|引数の関数の型|実際の関数の型|`A`の転送状況|
|--|--|--|
|std::function&lt;void(A)&gt;|void(A)|copy → move|
|std::function&lt;void(A)&gt;|void(const A&)|copy|
|std::function&lt;void(A)&gt;|void(A&&)|copy|
|std::function&lt;void(A)&gt;|void(auto)|copy → move|
|std::function&lt;void(A)&gt;|void(const auto&)|copy|
|std::function&lt;void(A)&gt;|void(auto&&)|copy|
|std::function&lt;void(const A&)&gt;|void(A)|copy|
|std::function&lt;void(const A&)&gt;|void(const A&)| |
|std::function&lt;void(const A&)&gt;|void(A&&)| (コンパイルエラー)|
|std::function&lt;void(const A&)&gt;|void(auto)|copy |
|std::function&lt;void(const A&)&gt;|void(const auto&)| |
|std::function&lt;void(const A&)&gt;|void(auto&&)| |

実際の関数の仮引数がどうあれ、一旦は `call()` にて実際の関数をもとに `std::function` が生成され、`A` の値は `std::function` を経由してから実際に関数に転送されますので、実体 `A` を受ければコピーが発生します。
その後、実際の関数への呼び出しが発生するので、至極当然な結果でした。

## ちなみに

上記の検証コードの `fn_t` と `fn_cref_t` で `std::function` を使用しなかった場合には下記のような結果となります。

```cpp
using fn_t      = void(A);
using fn_cref_t = void(const A&);
```

|引数の関数の型|実際の関数の型|`A`の転送状況|
|--|--|--|--|
|void(A)|void(A)|copy| |
|void(A)|void(const A&)| (コンパイルエラー)|
|void(A)|void(A&&)|(コンパイルエラー)|
|void(A)|void(auto)|copy|
|void(A)|void(const auto&)| (コンパイルエラー)|
|void(A)|void(auto&&)| (コンパイルエラー)|
|void(const A&)|void(A)| (コンパイルエラー)|
|void(const A&)|void(const A&)| | |
|void(const A&)|void(A&&)| (コンパイルエラー)|
|void(const A&)|void(auto)|| |
|void(const A&)|void(const auto&)| | |
|void(const A&)|void(auto&&)| (コンパイルエラー)|

`std::function` という緩衝材が無いので無慈悲な結果ですが、`void(A&&)` が一見オリジナルの`A`の右辺値を受けているようなミスリード（実際はコピーされた`A`の右辺値）が無くなるし、速度面でも有利になります。しかし、関数がキャプチャ指定子を有するラムダ式の場合（例えば`auto fn1 = [=](A){};` のような場合）、関数オブジェクトとして取り扱う必要があるので全てコンパイルエラーとなります。