C++はテンプレート関数を作ることができる
template<class T>void f(){}
##特殊化void f<int>(){}
特殊化はできる
template<class T>void f(){}
template<class T>void f<int>(){}
しかしこのような特殊化はできない
template<class T>void f<int,T>(){}
特殊化したい場合、オーバーロードを工夫する、間にクラスをかませるなどすることで代用できる
template<class>struct wrap{};
void impl(wrap<int>){std::cout<<"int";}
template<class T>void impl(T){std::cout<<"other";}
template<class>
struct impl2
{
static void output(){std::cout<<"other";}
};
template<>
struct impl2<int>
{
static void output(){std::cout<<"int";}
};
そして最終的にラップしてクサいにふたをすればよい
template<class T>void f(){impl(wrap<T>{});}
template<class T>void f(){impl2<T>::output());}
##部分特殊化void f(T*){}
このような特殊化は可能である
template<class T>
void f(T*){}
上記はポインタのみを引数にとる関数となる
template<class,class>wrap2{};
template<class T>
void f(wrap2<int,T>){}
上記はwrap2のみを引数にとるクラスとなる
これを利用してtemplateから型を引き出すこともできる
template<class,std::size_t>
struct List{};
template<class T,std::size_t N,std::size_t M>
List<int,N+M> f(List<T,N>,List<T,M>)
{return {};}
f(List<int,3>{},List<int,5>{});
##推測不可f(typename X<T>::Y)
template<class T>
struct ptr
{
using type=T*;
};
template<class T>
void f(typename ptr<T>::type)
{}
int x;
以下はコンパイルを通らない
f(&x)
ptr<T>::type==int
からTを推測することはできない
以下は通る
f<int>(&x)
ptr<int>::type==int
という問題が解決され引数int*の関数となる
struct hoge
{
struct piyo{};
};
template<class T>
void f(typename T::piyo)
{}
f(hoge::piyo{})
Tは推測できないためコンパイルに失敗する。piyoはhogeのことを知らない
以下は可能
f<hoge>(hoge::piyo{})
右辺値参照f(T&&)
template<class T>
void f(T&& x){}
上記は
int val;
f(42);//T==int x==int&&
f(val);//T==int& x==int&
となる
##オーバーロードの順番
オーバーロードの順番は大まかに決まってはいるが決まってないものにぶつかった場合エラーとなる(半順序)
オーバーロードのルールは複雑なので大まかに示す
f([int的なもの])
を実行したとき
1.f(int),f(int const),f(int&)
など参照,const系
2.f(T)
などtemplate系
3.f(double),f(long)
など変換可能なもの
4.f(...)
可変長引数(printfなどで使われるアレ)
が呼ばれる
1について{int&&,int&
}>int const&
となる
他細かいルールについては概ね直感的に動く
紛らわしいオーバーロードはしない方がよい
##戻り値推測auto f(),decltype(auto) f()
戻り値が推測できる。ただしC++14から
decltype(auto)
はdecltype(expr)のルールで動く
autoはconstや参照などが丸められる
##戻り値の型の後置auto f()->void{}
戻り値を後ろに書くことができる。decltype()を用いて書くときに便利、あと頭がすっきりして見やすい。戻り値の推測が使えるので必要ないかもしれない
##SFINAEstd::enable_if<>::type
「T::type f(T)
でTにtypeが存在しなくてもすぐにコンパイルエラーにせず合致するものを探そう」という試み。これを利用してfの処理を振り分けたりする
書き方はテンプレート引数に書いたり
template <typename T, typename std::enable_if_t<pred>* =nullptr>
void f(T t){}
戻り値に書いたり
template <typename T>
std::enable_if_t<pred,ReturnType> f(T t){}
引数に書いたり
template <typename T>
void f(T t,std::enable_if_t<pred>*=nullptr){}
ちなみに
template <typename T, class=std::enable_if_t<pred>>
void f(T t){}
template <typename T, class=std::enable_if_t<!pred>>
void f(T t){}
とすると失敗する
上の関数一つの時は問題ないが同じ関数を2度定義しようとしている扱いになり失敗する
classの時は使えるが関数の時この手法は使えない