C++ で型によるコンパイル時条件分岐技法まとめ

[ブログ記事](http://secret-garden.hatenablog.com/entry/2016/12/25/063658)からの転載です。

[初心者C++er Advent Calendar 2016](http://www.adventar.org/calendars/1691) 11日目の記事です。  
空いてるようなのでまた書きました。  
さて、初心者用ということで型を使用した『コンパイル時条件分岐技法』を簡単に紹介してみます。  

## [注意]

本記事では C++11 で動作することを想定してるコードになります。  

## [多重定義]

まずは一番よく利用されている多重定義ですね。  
C++ では引数の型を変えることで複数の関数を定義することが出来ます。  

```cpp

void
print(int n){
	std::printf("int 型:%d\n", n);
}

void
print(float f){
	std::printf("float 型:%f\n", f);
}

void
print(char const* str){
	std::printf("char const* 型:%s\n", str);
}

print(42);
print(3.14f);
print("homu");

/*
output:
int 型:42
float 型:3.140000
char const* 型:homu
*/
```

また、型以外に引数の数によっても複数の関数を定義することが出来ます。  

```cpp
int
func(int n){
	return n + n;
}

int
func(int n, int m){
	return n + m;
}

func(42);     // 84
func(4, 6);   // 10
```

まあ流石に C++ を書いてる人で多重定義を書いたことがない人はいないと思います。  

## [テンプレートの特殊化]

さて、次はテンプレートを使った条件分岐です。  
いわゆる『テンプレートの特殊化』と言われるやつですね。  
例えば、次のようなクラステンプレートがあったとします。  

```cpp
template<typename T>
struct X{
	int
	twice(){
		return value + value;
	}

	T value;
};

X<int> x{42};
x.twice();       // 84
```

この時にテンプレートが『任意の型』の場合に `twice()` の実装を変えたいとします。  
例えば、`T = char` の場合は `std::string` を返すようにしたい場合は `X<char>` に対応するクラスを新しく定義します。  

```cpp
template<typename T>
struct X{
	int
	twice(){
		return value + value;
	}

	T value;
};

X<int> x{42};
x.twice();       // 84


// 新しくクラスを定義する
// この template 引数は省略し
template<>
// ここに『任意の方』を記述する
struct X<char>{
	std::string
	twice(){
		return std::string{value} + value;
	}

	// ここも T 型から char 型に書き直す
	char value;
};

X<char> x2{'c'};
x2.twice();         // "cc"
```

こんな感じです。  
まあこれは『こういうものだ！』という風に覚えるしかないですね。  
また、『一部のテンプレート型に対して』も適用することが出来ます。  

```cpp
template<typename T, typename U>
struct X{
	
};

template<typename U>
// クラステンプレートの第一引数が int の場合のクラス
struct X<int, U>{
	
};
```

ここで注意するのは `template<>` の引数と `X<int, U>` の引数ですね。  
`template<>` の引数は任意で構いませんが、`X` に渡す引数の数は最初に定義したクラステンプレートと同じでなければなりません。  
なので、例えば、次のように拡張することは出来ません。  

```cpp
template<typename T>
// 引数の数が違うのでエラー
struct X<int>{
	
};
```

こんな感じですね。  
まあ使う機会は少ないかもしれませんが、覚えておくといざという時に利用できるかもしれません。  
ちなみに標準ライブラリの<del>悪名高い</del> `std::vector<bool>` もこのテンプレートの特殊化を利用してるクラスのひとつですね。  

## [SFINAE]

さて、SFINAE です。  

> 「SFINAE (Substitution Failure Is Not An Errorの略称、スフィネェと読む)」は、テンプレートの置き換えに失敗した際に、即時にコンパイルエラーとはせず、置き換えに失敗した関数をオーバーロード解決の候補から除外するという言語機能である。  

参照：[任意の式によるSFINAE - cpprefjp C++日本語リファレンス](https://cpprefjp.github.io/lang/cpp11/sfinae_expressions.html)

まあ、これ自体は難しい仕組みではなくて、例えば次のような関数テンプレートがあったとします。  

```cpp
template<typename T>
typename T::value_type
twice(T){
	return value + value;
}
```

これは、T 型に対して『`::value_type` を要求してる』という関数になります。  
なので、

```cpp
struct Int{
	using value_type = int;
	int value;
};

// Int 型は ::value_type があるので問題なく動作する
twice(Int{42});
```

というコードは問題ありませんが

```cpp
struct Float{
	float value;
};

// Float は ::value_type を持っていないのでエラー
twice(Float{42});

// int は ::value_type を持っていないのでエラー
twice(42);
```

というような引数は `::value_type` がないのでエラーになります。  
ここまではなんとなくわかると思います。  
では次に『`int` 型を受け取る関数』を追加します。  

```cpp
template<typename T>
typename T::value_type
twice(T t){
	return t.value + t.value;
}


// 新しく関数を追加
int
twice(int n){
	return n + n;
}


struct Int{
	using value_type = int;
	int value;
};

struct Float{
	float value;
};


// OK: T::value_type twice(T) が呼ばれる
twice(Int{42});     // 84

// OK: int twice(int) が呼ばれる
twice(42);          // 84

// Error: どっちの関数にもマッチしない
twice(Float{3.14f});
```

この時に `int` 型を引数に `twice()` を呼び出した場合に関数テンプレートで『`::value_type` がない』とエラーにならずに `twice(int)` が呼び出されます。  
つまり

> 即時にコンパイルエラーとはせず、置き換えに失敗した関数をオーバーロード解決の候補から除外するという言語機能である。  

と言うとおり『エラーにならないでオーバーロードから除外されるだけ』という挙動になります。  
SFINAE はよく `enable_if` などと組み合わせて利用されるためちょっとコードが複雑になってしまいますが、SFINAE 自体の仕組みはこれだけです。  
そんなに難しくはないでしょ？
また、上記の例では『戻り値型に `T::value_type`』を定義していましたが、他にも

```cpp
template<
	typename T,
	// テンプレートのデフォルト引数として定義
	typename Result = typename T::value_type
>
Result
twice(T t){
	return t.value + t.value;
}
```

このように『テンプレートのデフォルト引数』として定義してみたり、

```
template<typename T>
void
print(T t, decltype(t.value)* = 0 /* メンバ変数 value にアクセスする */){
	std::cout << t.value << std::endl;
}
```

これならば『T がメンバ変数 value を持ってる』みたいな条件付けをすることも出来ます。  
これにより『単純な型』だけではなく『より複雑な条件』で多重定義を行うことができるようになります。  
このようなテクニックはいくかありますが、詳しくは["任意の式によるSFINAE"](https://cpprefjp.github.io/lang/cpp11/sfinae_expressions.html)を参照してみてください。  

## [まとめ]

と、言う感じで簡単にコンパイル時条件分岐技法をまとめてみました。  
正直 SFINAE に関してはぜんぜん紹介しきれてないですが、まあ初心者向けということで『こういうのもあるんだよー』ぐらいに思っておけばいいと思います。  
SFINAE に関しては余裕があればまた別の Advent Calendar で記述したいと思います。