C++ で楽しいメタプログラミングをしよう

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

[初心者C++er Advent Calendar 2016](http://www.adventar.org/calendars/1691) 19日目の記事です。  
クリスマスから少し過ぎてしまいましたが、最後に楽しくメタプログラミングしてみる記事でも。  
<del>初心者C++er Advent Calendar 2016 が全部うまるといいですね。</del>

## [メタプログラミングとは]

メタプログラミングの定義は言語によって異なりますが、C++ においては『テンプレート』を利用して『コンパイル時』に処理を行うことを指す事が一般的です。  
今回はテンプレートを利用して『型に対して』コンパイル時に処理を行うコードを書いていきたいと思います。  

## [前提の知識]

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

前提として『テンプレートの特殊化』を理解している必要があります。  
『テンプレートの特殊化』というのはクラステンプレートなどに対して『特定の型』に対して処理を別に記述する記法です。  

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

// 任意のテンプレート引数型に対して拡張する
// テンプレート引数が int の場合はこっちが使用される
template<>
struct X<int>{
	
};
```

メタプログラミングはこの特殊化を利用することが多いです。  

## [`static_assert()`]

`static_assert()` は『コンパイル時に意図的にエラーにするため』の機能です。  

```cpp
static_assert(定数式, 文字列リテラル);
```

のような形式で記述し、`定数式`が `false` の場合に `文字列リテラル` を出力してコンパイルエラーになります。  

```cpp
static_assert(3 > 1, "Error Message");    // OK コンパイルエラーにならない
static_assert(1 > 3, "Error Message");    // NG コンパイルエラーになる
```

メタプログラミングは `static_assert()` を使用してテストする事が多いです。  

参照：[コンパイル時アサート - cpprefjp C++日本語リファレンス](https://cpprefjp.github.io/lang/cpp11/static_assert.html)


### [`std::true_type` と `std::false_type`]

今回つくるメタプログラミングは `std::true_type` と `std::false_type` を使用します。  
この2つの型は単純に `::value` が `true` と `false` を返すだけのクラスになります。  

```cpp
std::true_type::value;    // true
std::false_type::value;   // false
```

この2つは標準ライブラリの `<type_traits>` で
具体的な利用方法はこのあと書いていきます。  

### [メタ関数]

メタプログラミングはクラステンプレートに型を渡して処理を行ないます。  

```cpp
// hoge クラステンプレートに型を渡してその結果を ::type で取得する
hoge<int, float>::type
```

このように任意の型をクラステンプレートに渡して、その結果を `::type` という型で受け取ります。  
また、このように型に対して処理するクラステンプレートのことを『メタ関数』を呼びます。  
ちなみにメタ関数には型以外にも『値』を返すこともあります。  
その場合は

```cpp
foo<int, float>::value
```

というふうに `::value` で返すことが一般的です。  
このようにメタ関数を使用、定義する場合はそのメタ関数が『型を返す』のか『値を返す』のかを意識する必要があります。  

## [型を比較する]

さて、まずは簡単に型を比較してみましょう。  
動作イメージは以下のような感じです。  

```cpp
template<typename T, typename U>
struct is_same;

is_same<int, int>::value;         // true
is_same<int, float>::value;       // false
is_same<int, const int>::value;   // false
```

このようにある型とある型が同じ型かどうかを比較するクラステンプレートを書いてみます。  

```cpp
#include <type_traits>

// 元となるクラステンプレート
// デフォルトでは std::false_type を継承する
template<typename T, typename U>
struct is_same : std::false_type{};

template<typename T>
// 2つのテンプレート引数が同じ型の場合の特殊化
// 同じ型の場合は std::true_type を継承する
struct is_same<T, T> : std::true_type{};


// ::value は std::true_type と std::false_type で定義されてる
static_assert(is_same<int, int>::value       == true, "");
static_assert(is_same<int, float>::value     == false, "");
static_assert(is_same<int, const int>::value == false, "");
```

http://melpon.org/wandbox/permlink/VNE2apvsZ2Hq21Le

こんな感じでテンプレートの特殊化を利用して実装します。  
今回は比較した結果を『値』で返すので `::value` で結果を受け取ります。  
ポイントとしては真になる場合は `std::true_type` を継承し、偽になる場合は `std::false_type` を継承してる点です。  
これにより `::value` を定義することなく継承するだけで『真』や『偽』となるクラスが定義できます。  

## [型に `const` などを付属させる]

任意の型に対して `const` やポインタなどを付属させるメタ関数を定義してみます。  
まずはポインタを追加するメタ関数です。  

```cpp
template<typename T>
struct add_const{
	using type = T const;
};

static_assert(is_same<add_const<int>::type, int const>::value, "");
static_assert(is_same<add_const<int*>::type,  int* const>::value, "");
static_assert(is_same<add_const<int const>::type, int const>::value, "");


template<typename T>
struct add_pointer{
	using type = T*;
};

static_assert(is_same<add_pointer<int>::type,    int*>::value, "");
static_assert(is_same<add_pointer<void>::type,   void*>::value, "");
static_assert(is_same<add_pointer<float*>::type, float**>::value, "");

// 組み合わせて使用したり
static_assert(is_same<add_pointer<add_const<int>::type>::type, int const*>::value, "");
```

http://melpon.org/wandbox/permlink/GtOPyLNLgxyJK2jX

今回は結果が『値』ではなくて『型』なのでテンプレート型に対して `const` や `*` を付属した `type` 型を定義しています。  

### [`const` やポインタを削除する]

では逆に `const` やポインタを削除するメタ関数を定義してみましょう。  

```cpp
template<typename T>
struct remove_pointer{
	using type = T;
};

template<typename T>
struct remove_pointer<T*>{
	using type = T;
};

static_assert(is_same<remove_pointer<int*>::type,  int>::value, "");
static_assert(is_same<remove_pointer<int**>::type, int*>::value, "");
static_assert(is_same<remove_pointer<int>::type,   int>::value, "");


template<typename T>
struct remove_const{
	using type = T;
};

template<typename T>
struct remove_const<T const>{
	using type = T;
};

static_assert(is_same<remove_const<int const>::type,  int>::value, "");
static_assert(is_same<remove_const<int* const>::type, int*>::value, "");
static_assert(is_same<remove_const<int>::type,        int>::value, "");
```

http://melpon.org/wandbox/permlink/dmYesP6x8QV9GZOK

先ほどのコードよりもちょっと複雑ですね。  
`const` やポインタの判定に特殊化を利用しています。  

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

というように記述することで『ポインタ型の場合のみ』という特殊化を行うことが出来ます。  

## [コンパイル時 FizzBuzz]

さて、最後にコンパイル時に FizzBuzz を行うメタ関数を書いてみましょう

### [結果の型]

今回、FizzBuzz の結果は型として返します。  
ですので最初にそれぞれの結果を型として定義しておきます。  

```cpp
struct Fizz{};

struct Buzz{};

struct FizzBuzz{};

// Fizz, Buzz, FizzBuzz 以外の場合はこの型をラップして返す
template<int N>
struct int_{};
```

数値の場合は『値』ではなくて『型』を返す必要があるので、それ用に『数値を受け取るクラステンプレート』も用意しておきます。  

### [if_else]

分岐処理を行うために if 文を模倣するメタ関数も用意しておきます。  

```cpp
template<bool Cond, typename Then, typename Else>
struct if_{
	using type = Then;
};

template<typename Then, typename Else>
struct if_<false, Then, Else>{
	using type = Else;
};

static_assert(is_same<if_<true,  int, float>::type, int>::value, "");
static_assert(is_same<if_<false, int, float>::type, float>::value, "");
```

これも特殊化を利用してデフォルトは `Then` を返し、`Cond` が `false` の場合は `Else` を返すようにします。  

### [完成品]

先ほど書いたメタ関数を利用して FizzBuzz を実装すると以下のような感じになります。  

```cpp
struct Fizz{};

struct Buzz{};

struct FizzBuzz{};

template<int N>
struct int_{};


template<bool Cond, typename Then, typename Else>
struct if_{
	using type = Then;
};

template<typename Then, typename Else>
struct if_<false, Then, Else>{
	using type = Else;
};

static_assert(is_same<if_<true,  int, float>::type, int>::value, "");
static_assert(is_same<if_<false, int, float>::type, float>::value, "");


template<int N>
struct fizzbuzz{
	using type
		= typename if_<N % 15 == 0, FizzBuzz,
		  typename if_<N %  3 == 0, Fizz,
		  typename if_<N %  5 == 0, Buzz,
		  int_<N> >::type >::type >::type;

//  どっちが見やすいかな…
// 	using type = typename if_<
// 		N % 15 == 0,
// 		FizzBuzz,
// 		typename if_<
// 			N % 3 == 0,
// 			Fizz,
// 			typename if_<
// 				N % 5 == 0,
// 				Buzz,
// 				int_<N>
// 			>::type
// 		>::type
// 	>::type;
};

static_assert(is_same<fizzbuzz<1>::type,  int_<1>>::value, "");
static_assert(is_same<fizzbuzz<2>::type,  int_<2>>::value, "");
static_assert(is_same<fizzbuzz<3>::type,  Fizz>::value, "");
static_assert(is_same<fizzbuzz<4>::type,  int_<4>>::value, "");
static_assert(is_same<fizzbuzz<5>::type,  Buzz>::value, "");
static_assert(is_same<fizzbuzz<6>::type,  Fizz>::value, "");
static_assert(is_same<fizzbuzz<10>::type, Buzz>::value, "");
static_assert(is_same<fizzbuzz<15>::type, FizzBuzz>::value, "");
static_assert(is_same<fizzbuzz<30>::type, FizzBuzz>::value, "");
```

http://melpon.org/wandbox/permlink/pEnLxRi5MXxKJ83C

メタプログラミングではネストした if 文を記述するのが難読化してしまうのでちょっと見づらいですが、やってること自体は一般的な FizzBuzz と同じなのでそんなに複雑ではないです。  


## [まとめ]

と、言う感じで簡単にメタプログラミングについて書いてみました。  
C++ でメタプログラミングというと敷居が高いイメージがありますが、細かいところ見ていくとそんなに複雑なことをしていないことがわかると思います。  
メタプログラミングは C++ の醍醐味でもありますしね。  
今回の記事でメタプログラミングに興味を持った方は標準ライブラリの `<type_traits>` を調べてみたり Boost.MPL なんかを見てみるとよいと思います。  
ちなみに今回は C++03 を少し意識して書いたんですが、C++11/14 では少しメタプログラミングが記述しやすくなっているので、それについても調べてみても面白いです。  
それではよい C++ を〜。