C++関数テンプレートと半順序とオーバーロード

#C++関数テンプレートと半順序とオーバーロード
この記事は「プロ生ちゃん Advent Calendar 2015」の19日目の記事です
はじめまして、いなむ先生と申します
C++とテンプレートが大好きです

この記事では関数テンプレートに関して解説します

##関数テンプレート
関数テンプレートとは変数の型をテンプレート宣言したもの
C++98から導入された新機能である

```cpp
template < typename  T >
T max(T a, T b) ;
```

この場合には呼び出しの実引数から型推論が行われる

```cpp
max(1,2); // T is int
max(1.0, 3.0); // T is double
```

##オーバーロード
オーバーロードとは同じ名前を持つ引数の型が異なる関数を呼び分ける事ができる機能

```cpp
double max(double a, double b);  // #1

int max(int a, int b); // #2

max(1.0, 2.0); // calls #1
max(1,2) // calls #2
```

##半順序その前に
テンプレートは更に複雑な宣言が可能である
テンプレートの呼び分けを可能にする機能を紹介する

###明示的特殊化と部分的特殊化
引数が特定の型の場合に呼び出すテンプレートを指定することができる
これを関数テンプレートの明示的特殊化という

```cpp
template < typename T >
T f(T a, T b)
{
    return a + b ;
}

template < >
int f<int>(int a, int b)
{
    return a * b ;
} 
```

テンプレートであることを示すためにtemplate宣言は省略できない
`< >`の中身をカラで宣言する
さらに、関数名に続いて`<>`を書きその中に特別扱いする型を記述する

これを利用して特定の型で関数テンプレートが呼ばれた場合にコンパイルエラーとすることができる

```cpp
template < typename T >
T f(T a, T b)
{
    return a + b ;
}

template < >
int f<int>(int a, int b) = delete ;
```

上の例では、intの場合には明示的特殊化が優先的に呼ばれるが__deleted宣言__されているためにコンパイルエラーとなる

次のような特殊化を行うことはできない

```cpp
template < typename L, typename R >
void f(L lhs, R rhs) ;

template < typename L >
void f<L,int>(L lhs, int rhs) ; // Error ! 

template < >
void f<int,int>(int lhs, int rhs) ; // OK ! 
```
2つ目の特殊化を部分的特殊化という
これはクラステンプレートではできるが、関数テンプレートでは許可されていない
具体的にテンプレート仮引数を指定する特殊化の場合、テンプレート仮引数をすべて指定しなければならない

次のようにオーバーロードは行うことができる

```cpp
template < typename T >
void f(T* p) ; // Tのポインタのみを引数に取る

```

多くの場合には部分的特殊化ではなく、オーバーロードで代用できます

N4140 14.8.2.5 Deducing template arguments from a type
より引用すれば
推論されるテンプレートの宣言は以下のとおり

>- T
- cv-list T
- T*
- T&
- T&&
- T[integer-constant ]
- template-name <T> (where template-name refers to a class template)
- type (T)
- T()
- T(T)
- T type ::*
- type T::*
- T T::*
- T (type ::*)()
- type (T::*)()
- type (type ::*)(T)
- type (T::*)(T)
- T (type ::*)(T)
- T (T::*)()
- T (T::*)(T)
- type [i]
- template-name <i> (where template-name refers to a class template)
- TT<T>
- TT<i>
- TT<>

Tはテンプレート引数
TTはテンプレートテンプレート引数
iは非型テンプレート引数
(T)は少なくともひとつ以上の引数を持つ関数の引数リスト
()は引数のない関数の引数リスト
<T>は少なくともひとつ以上のテンプレート引数を持つテンプレート引数リスト
<>はテンプレート引数を持たないテンプレート引数リストである

対して、テンプレートが決して推論されない場合がある

- ネストした名前にテンプレート引数が使われてい  
::の左側にテンプレート仮引数がある場合と思っていい

```cpp
template < typename T >
void f( typename hoge<T>::type ){} // non-deduced contexts
```

- decltype式(C++14以降)  
`decltype`式は推論の対象にならない

- 非型テンプレート引数または配列の要素数がテンプレート引数を参照する部分式である  

```cpp
template< std::size_t N >
void foo( int(&)[2*N] ){} // error

template< std::size_t N >
void bar( int(&)[N] ){} // OK
```
[N]はOKで
[2*N]は部分式なのでアウト

- 推論に使用される引数において、デフォルト引数が適用になる  
デフォルト引数から推論することができない

```cpp
template < typename T >
void f( T = 0 ) ;

f() // error!
```

- 初期化リストが指定された場合  
初期化子リストをinitializer_listと推論できない

```cpp
template < typename T >
void f(T) ;

f({1,2,3}); // error!
```

##半順序
通常の関数、テンプレートが混在した場合はどれが呼ばれるのか？

おおまかに次のようになる

`f( [int] )` を実行したとき（`[int]`はintがcvr修飾されたもの）

1. `f(int)`,`f(int const)`,`f(int&)`等、intがcvr修飾されたもの
2. `f<int>(int)`などの特殊化されたテンプレート
3. `f(T)`,などtemplate
4. `f(double)`,`f(long)`など型変換可能な引数を持つもの
5. f(...)可変長引数

という順序で呼ばれる
この順序大まかにしか決まっておらず、
推論に一致するものがないまたは、推論が複数のテンプレートに一致する場合においてはコンパイルエラーとなる

1.の場合においては、実引数がcv修飾されている場合にはcv修飾された関数が優先的に呼ばれる
テンプレートでも同じことができる

##関数の戻り値推論など

###関数の戻り値を推測する事ができる

```cpp
template < typename L, typename R >
auto f(L&& a, R&& b)
{
    return a + b ;
}
```

この場合は`a + b`から型が推測されるがvc修飾は型に含まれない

関数の戻り値をcv修飾を含めた完全な型で推論するためには`decltype(auto)`を用いる

```cpp
template < typename L, typename R >
decltype(auto) f(L&& a, R&& b)
{
    return a + b ;
}
```

ただし、`decltype(auto)`には落とし穴があり
戻り値を`()`で囲んだ場合に起こる

C++では 変数`x`は左辺値であり、`(x)`も左辺値である
このことによって`x`が`int`の場合
```decltype(x) = int```
であり
```decltype( (x) ) = int&```
である

```cpp
template < typename L, typename R >
decltype(auto) f(L&& a, R&& b)
{
    return (a + b) ;
}
```

このようなコードを書いた場合`a + b`の結果というローカルオブジェクトを参照してしまう

###関数の戻り値を後置する

```cpp
template < typename L, typename R >
auto f(L&& a, R&& b)->decltype(a + b)
{
    return a + b;
}
```

このように書くことで、戻り値を後置することができる
単にreturn文から推論する場合は```decltype(auto)```を用いれば良い
同じ式を二度も書くのはカッコ悪い

##SFINAE
SFINAEとは
__Substitution Failure Is Not A Error__
の略語である
日本語にすると
「テンプレートのインスタンス化の失敗はエラーではない」
となる

テンプレートのインスタンス化に失敗しても、即座にエラーにならず
オーバーロードの候補から外れる
そうしておいて、更に一致するテンプレートを探すことができる

これを~~悪用~~ 利用すると任意の場合にオーバーロードを分岐させる事ができます

`std::enable_if<>`を用いて特定の条件をメタ関数などで記述することで
条件に合致した場合にテンプレートのインスタンス化を失敗させることで
オーバーロードの候補から外すことができる

テンプレート仮引数にSFINAE

```cpp
template <typename T, std::enable_if_t<pred>*& =nullptr_t>
void f(T t){}
```

戻り値にSFINAE

```cpp
template <typename T>
std::enable_if_t<pred,ReturnType> f(T t){}
```

引数にSFINAE

```cpp
template <typename T>
void f(T t,std::enable_if_t<pred>*=nullptr){}
```

更に戻り値の後置宣言で用いる`decltype`と合わせる事ができる
今回はメンバ関数にfindを持つ`map`と、持たないコンテナにおいて
統一して要素を検索できるように`my_find`を書いてみた

```cpp
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <map>

template < typename Container >
auto my_find(Container& c, typename Container::key_type key)
    ->decltype((c.find(key)))
{
    std::cout << "member" << std::endl;
    return (c.find(key));
}
template < typename Container >
decltype(auto) my_find(Container& c, typename Container::value_type v)
{
    std::cout << "free" << std::endl;
    return (std::find(c.begin(),c.end(), v)) ;
}

int main(){
    std::vector<int> a{1,2,3};
    std::map<int,int> b;
    b[0] = 1;
    b[2] = 3;
    b[4] = 5;
    
    auto&& iter1 = my_find(a,2);    
    std::cout << ( iter1 == a.end() ? "not found" : "found" ) << std::endl;
    std::cout << std::endl;
    auto&& iter2 = my_find(b,2);
    std::cout << ( iter2 == b.end() ? "not found" : "found" ) << std::endl;
    
    return 0;
}


```

実行結果
>free
found

>member
found

同じ呼び出しで範囲から見つかった場合は要素を指すイテレータ
見つからなかった場合は`end`を指すイテレータが返る
こんなややこしいことをして何が楽しいのかは放っておいてください

密かに、第二引数はテンプレートの推論ルールでは推論されません
依存型です
これを推論可能なテンプレートにした場合にはkey_type や value_type
との整合性が取れなかった場合にはコンパイルできなくなるため
ちょっとしたテクニックですかね？

テンプレートは便利ですが、SFINAEなど
本来なら条件分岐の文法ではないものをオーバーロードの条件分岐に用いたり
メタプログラミング方面で進化を遂げすぎたため
永遠に玄人のテクニックである気がします

`static_if`が渇望される！


##跋文

最後まで読んでくださった方ありがとうございました
突貫で執筆したためチェックが行き届いておりません
間違いがありましたらコメントをよろしくお願いします