meryngii.neta

今日も新たな"ネタ"を求めて。

Template Aliases

C++11 Advent Calenderの26日目の記事です。
クリスマスまで残り365日と迫りましたね。C++11 Advent Forever.

さて、C++11で導入された機能の一つがTemplate Aliasesです。元々はTemplate Typedefなどとも呼ばれていました。他の主要な機能と比べて大して目立っていないですが、細かいポイントがいくつかあるような気がします。

Template Aliasesとは?

C言語でもtypedefを使えば型に好きな別名を付けることができます。C++のクラスも型ですからtypedefできます。ではテンプレートクラスはどうかというと、C++03まではそのままの名前で使うしか無かったのです。Template Aliasesならテンプレートに別名を与えることができます。そう、C++11ならね。

template <typename T>
using Vec = std::vector<T>; // Template Alias

Vec<int> v; // OK: std::vector<int> v; と同じ

あらかじめ引数を当てはめておくこともできます。

// 文字列をキーとする辞書(本当はC++11ならunordered_mapがオススメ)
template <typename T>
using Dictionary = std::map<std::string, T>;

Dictionary<double> dic;
// std::map<std::string, double> dic; と同じ

もう一つ例を挙げると、自作のアロケータを簡単に使えるというものがあります。

template <typename T>
class MyAllocator { /* ぼくの考えたさいきょうのアロケータ */ };

template <typename T>
using MyVector = std::vector<T, MyAllocator<T>>; // アロケータを埋め込む

MyVector<int> v; // 指定しなくても上のアロケータを使える

ここまでは割と簡単な例ですが、もっと複雑な型も自在に表現できます。なぜかというとメタプログラミングで作った型を取り出すことができるからです。

#include <type_traits> 

// constを取り除く
std::remove_const<const int>::type x1; // int x1;

template <typename T>
using remove_const = typename std::remove_const<T>::type; // typenameは必要

remove_const<const int> x2; // int x2;
typeが要らなくなるだけでも大分格好良くなります。

typedefを置き換える(普通の型のエイリアス

Template Aliasesはテンプレート引数+エイリアスという形で成り立っているので、エイリアスの部分だけ使用するとtypedefの代わりとして使えます。

typedef int intA_t;  // 古いシンタックス(C or C++03)
using intB_t = int;  // 新しいシンタックス(C++11)

これによって、C言語邪悪なシンタックス*1から幾分か解放されることになります。

typedef double (*func1A_t)(int);          // どれが新しい型名だろう?
using func1B_t = double (*)(int);         // 新しい型名が分かりやすい
using func1C_t = auto (*)(int) -> double; // 戻り値の後置も使ってみる

// intを引数に取って、doubleの配列への参照を返す関数へのポインタ
typedef double (&(*func2A_t)(int))[100];  // オエーッ
using func2B_t = double (&(*)(int))[100];
using func2C_t = auto (*)(int) -> double (&)[100]; // 若干読みやすい?

戻り値の後置については省略*2。配列への参照は昔書いたネタです。
alias declarationによって、typedefはCとの互換性以外の必要性が無くなりました。ナウでヤングなC++11プログラマはusingを使うべきなのです。数十年後typedefは絶滅する運命にあるのです。

変数のエイリアスは作れません

ところでよくある次のようなusing declaration

using std::string;

は、新しく導入されたエイリアスによって

using string = std::string;

とと同じになります。つまり、型のusing declarationはalias declarationの糖衣構文とも考えることができるらしいのです。
しかし、右辺に取れるのは型だけなので、

using std::cout; // OK
using Cout = std::cout; // エラー

となって変数では同様の考え方が成り立たないということになります。変数のエイリアスは十分なメリットがないとして導入されていません。

Template Aliasesの特殊化はできません

Template Aliasesは特殊化できません。例えば次のようなコードは不正です:

// 空想のコード
template <int> using int_exact;
template <> using int_exact<8> = signed char;
template <> using int_exact<16> = short;

int_exact<16> val; // short val;

しかしクラスの特殊化と組み合わせれば、

// コンパイルできる
template <int> struct int_exact_traits;
template <> struct int_exact_traits<8> { using type = signed char; };
template <> struct int_exact_traits<16> { using type = short; };

template <int N>
using int_exact = typename int_exact_traits<N>::type; // OK

int_exact<16> val; // short val; と同じ

このようなコードが書けるので問題ないわけです。Template Aliasesの特殊化が認められていない理由は、クラステンプレートの特殊化・エイリアスの特殊化の2つが入り乱れて混乱することを防ぐためだそうです。

Template Template Parametersに投入する

面白いことに、Template Aliasesはテンプレート実引数にできると標準で定められています。
c++ - Can I use template aliases as template template parameters? - Stack Overflow
これによってTemplate Template Parametersを使う際の問題点が改善されます。
Template Template Parameters - Faith and Brave - C++で遊ぼう

// (型ではなく)テンプレートクラスを引数に取る
template <template <typename> class Container>
class A { /* ... */ };

template <typename T>
using MyVector = std::vector<T, MyAllocator<T>>; // 先ほどの例

A<std::vector> a1; // エラー
A<MyVector> a2;    // OK!!!

そのままのstd::vectorはテンプレート引数が2つ必要ですから、Aは不正です。しかしTemplate Aliasesでラップしてあげれば、どんなテンプレートでも変形して与えることができるのです。

Template Template Parametersの応用というと真っ先にLokiのポリシーが思い浮かびますが、他はパッと思いつかないです。Boostのどこかで使われているかもしれません。

Template Aliasesを継承で置き換えられるか?

Template Aliasesが存在しなかったころにも代替策がいくつかありました。
http://msdn.microsoft.com/ja-jp/library/cc440199%28v=vs.71%29.aspx

  • マクロを使ってみる。(ダサい上に問題が多い)
  • テンプレートクラスで派生してみる。
  • traitsをそのまま利用する。仕方なく::typeを書く。Type Generatorとも言うらしい。

ここで派生に目を向けると、C++11ではコンストラクタを継承できる*3のでより簡単に実現できます。

template <typename T>
class MyVector
    : public std::vector<T>
{
    using Base = std::vector<T>; // ここでもAlias Declaration

public:
    using Base::Base; // コンストラクタの継承
};

いわゆるStrong Typedefと同じで、元の型とは異なる型になります。ただこれだけだと何か足りないかもしれないです。

まとめ

Template AliasesはGCC 4.7から導入されています。この記事に使用したコンパイラgcc version 4.7.0 20111217 (experimental)です。
Inheriting Constructorsはまだ実装されていないようです。もうそろそろ実装されると思うのですが…。

参照リンク

まず最初に読むならDr.StroustrupC++11 FAQが簡潔で分かりやすいです。他の項目も合わせてオススメです。
http://www2.research.att.com/~bs/C++0xFAQ.html#template-alias
詳しい解説は提案に書いてあります。
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1489.pdf
その他色々参考にしました。
GCC-4.7 に Alias declarations (Template aliases) が実装されていた - 野良C++erの雑記帳
Template Aliases すごい - C++でゲームプログラミング

次回予告

明日27日目のC++11 Advent CalenderはT.MURACHIさんです。乞うご期待!

*1:D&E 2.8.1も参照のこと。

*2:C++0x 新たな関数宣言構文 - Faith and Brave - C++で遊ぼうなど。引数に依存した戻り値を書きたいのが元々の動機ですが、スコープを解決できるなど他にも利点があります:http://www2.research.att.com/~bs/C++0xFAQ.html#suffix-return

*3:C++0x 継承コンストラクタ(Inheriting Constructors) - Faith and Brave - C++で遊ぼう