C++ テンプレートの種類と構文
前回テンプレートがなぜ必要なのかについて簡単にまとめたので、今回はその構文や種類についてまとめたいと思います。
アウトライン
- テンプレートの種類と構文
- 定義する
- 使用する(インスタンス化)
- クラステンプレート
- 関数テンプレート
- メンバテンプレート
- エイリアステンプレート(C++11〜)
- 変数テンプレート(C++14〜)
- まとめと今後
テンプレートの種類と構文
C++ のテンプレートは,大きく 5 種類に分類することが出来ます。
これらについて、以降で詳しくまとめていきます。
まずざっくり共通するシンタックスを示しておきます。
定義する
何かのテンプレートを定義したい場合は、通常の定義の前に template
宣言を記述します。
template <typename T> class stack { ... };
複数のテンプレート引数を取りたい場合や、型以外のテンプレート引数を取りたい場合には、以下のようにします。
template <typename T, int N> class my_array { ... };
上の例はクラステンプレートでしたが、関数でもエイリアスでも、template
を宣言する部分は共通です。
使用する(インスタンス化)
次にテンプレートを使用する場合です。
テンプレートはあくまでテンプレートなので、使用する際には、具体的なテンプレート引数を与えて実体化する必要があります。これを インスタンス化 といいます。
インスタンス化の構文も、テンプレートの種類によらず基本的には共通しています。
stack<int> int_stack; my_array<std::string, 5> five_strings;
テンプレート名 < 引数 >
という感じです。
一応注意書きをしておきますと、stack<stack<int>>
がシンタックスエラーになる場合があります。
意味としては stack<int>
のスタックです。
stack<stack<int>>
がコンパイルエラーになる場合は、使っているコンパイラが古いかもしれません。
C++03 では、>>
の部分がシフト演算子として解釈されてしまうためです。
g++ -std=c++11
とか clang++ -std=c++11
とか g++ -std=c++14
とか clang++ -std=c++14
とか、コンパイラが新しい規格を参照するようにオプションを渡してあげれば動きます。
(もし動かない場合はコンパイラが古すぎます。もう 2016 年ですから、最低でも C++11 以降を使いましょう。別物です。)
では、以降、それぞれのテンプレートについて詳しく見ていきます。
クラステンプレート
クラステンプレートは、クラスのテンプレートです。前回の stack
がこれにあたります。
一番わかり易いユースケースは、コンテナ型の定義でしょう。
スタックや単方向リスト、ハッシュマップなど、内部に保持する型に依存しないデータ構造を定義するために使えます。
前回の stack
を再掲しておきます。
template <typename T> class stack { public: stack() : data(), n() {} void push(T x) { if (n >= MAX_ELEM) { throw "stack is full!!"; } data[n++] = x; } T pop() { if (n < 0) { throw "stack is empty!!"; } return data[--n]; } private: T data[MAX_ELEM]; int n; };
関数テンプレート
関数テンプレートは以下の様なものです。
template <typename T> T max(T left, T right) { if (left > right) { return left; } else { return right; } } int x = max<int>(1, 0); // => x == 1 double y = max<double>(0.5, 100.0); // => y == 100.0
max
関数は、「ある型 T について、2つの引数のうち、大きい方を返す」関数です。
明示的に max<int>
や max<double>
とすることで、int
や double
についての「大きい方を返す」関数を得ています。
テンプレート引数の推論
実は、関数テンプレートの場合、明示的にテンプレートを引数を渡す必要がない場合があります。
今回の max
関数はまさにそのケースです。
int x = max(1, 0);
これは、テンプレート引数の型推論の結果です。
max
関数の第一引数、第二引数はそれぞれ T
です。そして、実際に渡されている 1
や 0
は int
型です。
これらの情報から、コンパイラは T == int
であることを推論します。
したがって暗黙に max<int>
と指定されることになります。
この推論は色々複雑だったりしますが、はじめは引数から単純に推論できれば推論されると思っておけば良いんじゃないかなと思います。
一方、クラステンプレートなど、関数テンプレート(とメンバ関数テンプレート)以外のテンプレートの場合には、この推論は行われません。
勘違いしやすいので気をつけましょう。特にクラステンプレートは間違いやすいです。
メンバ関数テンプレート
class printer { public: explicit printer(std::ostream& os) : os(os) {} template <typename T> void print(T const& v) { os << v; } private: std::ostream& os; };
メンバ関数テンプレートは、関数テンプレートとほとんど同じです。違いは、メンバ関数として定義されていることだけです。
printer p(std::cout);
ここまではテンプレートでもなんでもないただのクラスです。
p.print(0); p.print("abc"); p.print<double>(0.1);
こんな感じで使います。関数テンプレートとほぼ同じですね。
エイリアステンプレート
エイリアステンプレートは、C++11 から使用できるテンプレートです。
template <typename T, typename U> struct pair; template <typename T> using with_int_t = pair<T, int>;
using
で型名のエイリアスを作ることが出来ますが、それをテンプレートにすることが出来ます。
with_int_t<bool> p(true, 0); // pair<bool, int> p(true, 0); with_int_t<std::string> s("abc", 100); // pair<std::string, int> s("abc", 100);
ちなみに C++11 より前は、エイリアステンプレートの代替として、
template <typename T> struct with_int { typedef pair<T, int> type; } with_int<bool>::type p(true, 0); // pair<bool, int> p(true, 0);
という記述をしていました。(今でもライブラリなどで現役の表現ですので覚えておきましょう)
変数テンプレート
最後の変数テンプレートは、C++14 から使用できるテンプレートです。
template <typename T> constexpr T pi = static_cast<T>(3.1415926); int x = pi<int>; double y = pi<double>;
constexpr
は定数式というやつです。
ちなみに C++14 より前は、代替として
template <typename T> struct pi { static const T value = static_cast<T>(3.1415926); }; int x = pi<int>::value; double y = pi<double>::value;
という記述をしていました。(こちらも現役の表現です)
まとめと今後
というわけで今回はテンプレートの種類とそれぞれのシンタックスについてまとめてみました。
今後、特殊化や部分特殊化などのお話をするときに種類によって微妙に違いがあったりするので、しっかり区別しておいたほうが良さそうです。