C++と色々

主にC++やプログラムに関する記事を投稿します。

型特性によるオーバーロード

型特性によるオーバーロードはいくつか方法がありますが、目的に合わせた方法をとるのが良いと思います。

・何通りかの型特性でオーバーロードをしたい場合
・ある型特性かどうかでオーバーロードしたい場合
・ある型特性でしかオーバーロードしてほしくない場合

の3つの場合に分けて考えてみます。

何通りかの型特性でオーバーロードしたい場合

普通にenable_ifを使ってオーバーロードをする。

#include <type_traits>
#include <iostream>

template <class T>
void test(T, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
	std::cout << "Tはクラス\n";
}

template <class T>
void test(T, typename std::enable_if<!std::is_class<T>::value>::type* = nullptr)
{
	std::cout << "Tはクラスではない\n";
}

struct hoge{};

int main()
{
	test(3);
	test(hoge());
}

実行結果

Tはクラスではない
Tはクラス

適宜enable_ifのテンプレート引数を型特性メタ関数に置き換えれば良いと思います。
関数の引数ではなく、テンプレート引数でオーバーロードする方法もあります。

#include <type_traits>
#include <iostream>

extern void* enabler;

template <class T, typename std::enable_if<std::is_class<T>::value>::type*& = enabler>
void test(T)
{
	std::cout << "Tはクラス\n";
}

template <class T, typename std::enable_if<!std::is_class<T>::value>::type*& = enabler>
void test(T)
{
	std::cout << "Tはクラスではない\n";
}

struct hoge{};

int main()
{
	test(3);
	test(hoge());
}

実行結果

Tはクラスではない
Tはクラス

詳しくは本の虫: C++0xにおけるenable_ifの新しい使い方を見て下さい。
1つめの例は引数の数が決まっている演算子のオーバーロードやコンストラクタには使いづらいものですけど、2つめは問題なく使えるという差があります。
ちなみに2つめの例はVC++11.0ではC2668エラーでコンパイル通りませんでした。g++4.6.2では問題なく動きました。
2013/7/1追記
VC++12.0(PR)ではちゃんと動きました。

ある型特性かどうかでオーバーロードしたい場合


つまり全部で2通り(その型特性か否か)でオーバーロードをしたい場合は、タグ・ディスパッチによるオーバーロードが良いと思います。

#include <type_traits>
#include <iostream>

template <class T>
void test_impl(T, std::true_type)
{
	std::cout << "Tはクラス\n";
}

template <class T>
void test_impl(T, std::false_type)
{
	std::cout << "Tはクラスではない\n";
}

template <class T>
void test(T t)
{
	test_impl(t, std::is_class<T>());
}

struct hoge{};

int main()
{
	test(3);
	test(hoge());
}

実行結果

Tはクラスではない
Tはクラス

標準の型特性メタ関数はtrue_typeかfalse_typeを継承しているのでこのようなことが行えます。Tがポインタ型かどうかぐらいならis_pointerを使うまでもなく、普通に部分特殊化すれば良いと思います。

ある型特性でしかオーバーロードしてほしくない場合


ある型特性を満たしたTしか適用したくない場合、enable_ifを用いたオーバーロードだとエラー文がわかりづらくなります。

#include <type_traits>

template <class T>
void test(T, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
}

struct hoge{};

int main()
{
	test(hoge());//OK
	test(3);//ERROR
}

エラー文(VC++11.0)

エラー	1	error C2893: Failed to specialize function template 'void test(T,std::enable_if<std::is_class<_Ty>::value,void>::type *)'
	2	IntelliSense: 関数テンプレート "test" のインスタンスが引数リストと一致しません
            引数の型: (int)	

このような場合はstatic_assertを使うとエラー文が分かりやすくなります。

#include <type_traits>

template <class T>
void test(T)
{
	static_assert(std::is_class<T>::value, "T must be class");
}

struct hoge{};

int main()
{
	test(hoge());//OK
	test(3);//ERROR
}

エラー文(VC++11.0)

エラー	1	error C2338: T must be class

参考
http://cpplover.blogspot.jp/2011/04/c0xenableif.html
http://flamingdangerzone.com/cxx11/2013/02/11/to-sfinae-or-not-to-sfinae