テンプレート関数、使ったことがないという方はいらっしゃらないと思いますが、特定の条件に当てはまるものしか受け取ってほしくない、あるいは、特定の条件に当てはまらないものしか受け取ってほしくないということが多々あると思います。
そのように必要とされる条件をコンセプトと呼びますが、今回はそのコンセプトを簡単に指定する方法をご紹介いたします。
使うものは <type_traits>のstd::enable_ifで、SFINAEを使うのですが、これがまた読みにくくなるテクニックなので、それを何をしてるのかわかりやすく、簡単にできるようにしたいと思います。
まず、肝になる型を定義します。std::enable_ifをちょっとラップするだけです。
#include <type_traits>
template < class Ty >
struct concept : std::enable_if < Ty::value, std::nullptr_t >
{ // determine if Ty::value is true
};
template < class Ty >
using concept_t = typename concept < Ty >::type;
これをテンプレート引数の最後に足して、SFINAEを実現します。
デフォルト引数にnullptrを渡してあげてください。
template < class Ty, concept_t < std::is_integral < Ty > > = nullptr >
Ty twice( Ty integral ) { return integral << 1; }
template < class Ty, concept_t < std::is_floating_point < Ty > > = nullptr >
Ty twice( Ty fp ) { return fp * 2; }
int main()
{
twice( 1 ); // twice with <<
twice( 1.0 ); // twice with *
}
twice関数に整数型を渡すと上のが、浮動小数点型を渡すと下のが、それ以外を渡すとコンパイルエラーになります。
このようにオーバーロードをすることもできますが、複数の関数に当てはまるテンプレート引数が推論されるとambiguousとしてコンパイルエラーになるので注意してください。
もっと複雑な条件を指定するときのために、ヘルパのメタ関数も一緒に置いておきましょう。
std::conditional_tはC++14からなので、必要であれば自前で定義してあげてください。
//
// meta_bool
template < bool b >
using meta_bool = std::integral_constant< bool, b >;
//
// meta_or
template < class A, class... Args >
struct meta_or
: std::conditional_t < A::value, std::true_type, meta_or < Args... > >
{ // if any of std::true_type in template arguments, this is derived from std::true_type.
};
template < class A >
struct meta_or < A >
: meta_bool < A::value >
{
};
//
// meta_not
template < class A >
struct meta_not
: meta_bool < !A::value >
{ // nagater
};
//
// meta_and
template < class... Args >
struct meta_and
: meta_not < meta_or < meta_not < Args >... > >
{ // if any of std::false_type in template arguments, this derive from std::false_type.
};
これらを使えば、複雑な条件も書くことができるかなと思います。
template < class Ty, concept_t < meta_not < std::is_pointer < Ty > > > = nullptr >
とすれば、ポインタ以外を受け取るようにできますね。
もっと実用的な使い道として、タグと何かクラスを関連づけてグループ分けしたり(例えばイテレータを使う関数など)、ある実装を持っているか(メンバ変数firstとsecond、メンバ型typeやvalue_type, element_type, メンバ関数begin, endなどなど…)で処理をわけたりすることができます。