関数テンプレートで「導出された型」と「値の型」の関係

## 検証方法

関数テンプレートの型引数 `T` に対して `foo(T v)`、`foo(const T& v)`、`foo(T&& v)` を呼び出した場合に、導出される `T` の型と関数に渡る値 `v` の型を調査しました。

`foo()` を呼び出すパターンは下記のとおりです。

```cpp
  const std::string a;
  std::string b;
  foo(a);
  foo(b);
  foo(std::string());
  foo(&a);
  foo(&b);
```

そして、具体的な検証コードは下記のとおりです。

```cpp:検証コード
#include <iostream>
using namespace std;

template <typename T> std::true_type  is_const_reference_func(const T&);
template <typename T> std::false_type is_const_reference_func(T&&);

template <typename T, typename U = decltype(is_const_reference_func(std::declval<T>()))>
struct is_const_reference : public U {};

/** @SaitoAtsushi さんのコメントで追記しました */
template <typename T> std::true_type  is_const_pointer_func(const T*);
template <typename T> std::false_type is_const_pointer_func(T&&);

template <typename T, typename U = decltype(is_const_pointer_func(std::declval<T>()))>
struct is_const_pointer : public U {};

template <typename T> void dump_attributes() {
  std::cout
    << "const=" << std::is_const<T>::value
    << ", const_ref=" << is_const_reference<T>::value
    << ", ref=" << std::is_reference<T>::value
    << ", lvalue_ref=" << std::is_lvalue_reference<T>::value
    << ", rvalue_ref=" << std::is_rvalue_reference<T>::value
    << ", const_ptr=" << is_const_pointer<T>::value /** @SaitoAtsushi さんのコメントで追記しました */
    << ", ptr=" << std::is_pointer<T>::value
    << std::endl;
}

template <typename T> void foo(T v) {
  std::cout << "foo(T)" << std::endl;
  std::cout << "T -> ";
  dump_attributes<T>();
  std::cout << "v -> ";
  dump_attributes<decltype(v)>();
  std::cout << std::endl;
}

// template <typename T> void foo(const T& v){
//   std::cout << "foo(const T&)" << std::endl;
//   std::cout << "T -> ";
//   dump_attributes<T>();
//   std::cout << "v -> ";
//   dump_attributes<decltype(v)>();
//   std::cout << std::endl;
// }

// template <typename T> void foo(T&& v) {
//   std::cout << "foo(T&&)" << std::endl;
//   std::cout << "T -> ";
//   dump_attributes<T>();
//   std::cout << "v -> ";
//   dump_attributes<decltype(v)>();
//   std::cout << std::endl;
// }

int main(void){
  const std::string a;
  std::string b;

  std::cout << "foo(a) -> ";
  foo(a);
  
  std::cout << "foo(b) -> ";
  foo(b);
  
  std::cout << "foo(std::string()) -> ";
  foo(std::string());
  
  std::cout << "foo(&a) -> ";
  foo(&a);
  
  std::cout << "foo(&b) -> ";
  foo(&b);
}
```

なお、`constの参照` は `std::is_const<>` で `false` と判定される [^1] ため、自作の `is_const_reference<>` を使用しています。

[^1]: https://cpprefjp.github.io/reference/type_traits/is_const.html

## 検証結果

### foo(T v)

|呼出方法|T|v|
|--|--|--|
| foo(a)             | std::string        | std::string  |
| foo(b)             | std::string        | std::string  |
| foo(std::string()) | std::string        | std::string  |
| foo(&a)            | const std::string* | const std::string* |
| foo(&b)            | std::string*       | std::string* |

#### 考察

ガッツリとコピーされ引数となります。ある意味、テンプレートの具体化のパターンが少なく、コンパイルされたバイナリの量が減るのがメリットかもです。（コピーするコードが増えるから微妙かな？）ただ、速度とかメモリの効率化という側面ではデメリットが大きいように思われます。
<strike>あれ？　`foo(&a)` って `const` 外れて良いのか？？</strike>（@SaitoAtsushi さんのコメントで凡ミス発覚、そして @myoukaku さんにトドメを刺されます）

### foo(const T& v)

|呼出方法|T|v|
|--|--|--|
| foo(a)             | std::string        | const std::string&  |
| foo(b)             | std::string        | const std::string&  |
| foo(std::string()) | std::string        | const std::string&  |
| foo(&a)            | const std::string* | const std::string*& [^2] |
| foo(&b)            | std::string*       | const std::string*& [^2] |

[^2]: `std::is_pointer<decltype(v)>::value` は `false` ですが、参照元がポインタなのでポインタの参照としています。

#### 考察

個人的には `T` と `v` の型の関係がしっくりきています。（この後の `foo(T&&)` と比較すると）
この `foo(const T&)` は、 `foo(T&&)` は同時に定義しても `ambiguous` というエラーにならず `foo(a)` のみ `foo(const T&)` が採用されます。[^3]
その際の `T` の型が異なる点に注意ですね。

[^3]: テンプレート関数の特殊化を行ったところ、２個の候補が存在する場合、より特殊化の度合いが高いテンプレート関数が採用される。（「ストラウストラップ著・プログラミング言語C++[第４版]・25.5.3 関数テンプレートの多重定義」の備考［2］より）


### foo(T&& v)

|呼出方法|T|v|
|--|--|--|
| foo(a)             | const std::string& | const std::string&  |
| foo(b)             | std::string&       | std::string&        |
| foo(std::string()) | std::string        | std::string&&       |
| foo(&a)            | const std::string* | const std::string*&& [^2] |
| foo(&b)            | std::string*       | std::string*&& [^2] |

#### 考察

ちゃんと `転送参照` [^4] の挙動が観測されていますね。（仕様なので当たり前ですが）
`foo(const T&)` より、 `T` と `v` の型にしっくり感がこないのは私だけでしょうか？
まぁ、メタプログラミングでは頻出だし、万能な気がしますが、`foo(std::string())` で `右辺値` を受けた際の `T` と `v` の型に注意ですかね。（`new T()`みたいな事をしたいのなら`std::remove_referece<>`とか`std::remove_const<>`を忘れずに[^5]）

[^4]: 関数テンプレートで型パラメータに `T` に `&&` を修飾した際の挙動は「[ユニバーサル参照(転送参照)](https://cpprefjp.github.io/lang/cpp11/reference_collapsing.html)」で、参照の定義や初期化の文脈において `&` と `&&` が重複した際の挙動は「[参照崩壊](https://cpprefjp.github.io/lang/cpp11/reference_collapsing.html)」と呼ばれます。

[^5]: そんな場合は `std::remove_const<typename std::remove_reference<T>::type>::type` とします。 `std::remove_const<>` と `std::remove_reference<>` の順番を逆にすると `const` が外れません。（  https://stackoverflow.com/questions/15887144/stdremove-const-with-const-references ）


## 所感

`foo(T&&)` のような `転送参照` は　`void*` 並に強い。（悪い意味じゃなく、間口が広い（寛容）という意味で）
ただ、関数テンプレートにおいて `転送参照` と `他` を比較すると、`型引数の型`と`値の型` に微妙にズレがあるように思います。
つまり、`T&&` と宣言しているのに `T` に参照が付いているのが原因です。

でも、ここにきて、 `std::forward<>` でこの `T` が役立つって訳か。[^7]
`static_cast<decltype(v)>(v)`ってやれば良いけど、回りくどいし、可読性悪いし。

個人的には、この検証で `std::forward<>` の実装と、実際どう作用しているのかが分かったので、やってみて良かった良かったです。（どうにも　`std::forward<T>` の `T` がどんな型なのか気になっていたんですよね）

[^7]: だとすると `右辺値` が渡ってきた時に `T` の参照が外れる仕様が疑問です。仕様通りの挙動なのは理解しているのですが、 `std::forward<>` の実装って `参照崩壊` を使っているので、他の参照と同様に `T` に `&&` を含めても良かったんじゃないかなと思うのです。何か他に理由があったのかな？　あ、特殊化によって展開（具体化）される数を節約しようという考えかも？