C++編【言語解説】 第22章 テンプレートパラメータ

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

型のテンプレートパラメータ

この章まで、テンプレートパラメータと言えば、次のように型を当てはめるものでした。

template <typename T>  // テンプレートパラメータ T
class DataStore {
};

template <typename T1, typename T2>  // テンプレートパラメータ T1、T2
void write_max(T1 a, T2 b);


DataStore<int> iStore;
write_max<int, double>(5, 5.5);  // 明示的な指定
write_max(5, 5.5);  // 自動的に推測

クラステンプレートの場合は、テンプレート引数は明示的に指定しなければなりませんが、 関数テンプレートの場合は、関数の実引数から自動的に推測させることもできます(第9章)。

ノンタイプテンプレートパラメータ

今度は、テンプレートパラメータが型でないパターンを取り上げます。 このようなテンプレートパラメータは、ノンタイプテンプレートパラメータ(非型テンプレートパラメータ)と呼ばれます。

単に「テンプレートパラメータ」と書いた場合、型であろうと型でなかろうと、クラステンプレートや関数テンプレートの宣言のところに書くパラメータのことを指しているか、 型によるテンプレートパラメータのことを指しているかのどちらかになります。 分かりづらいですが、文脈から区別は付くと思います。

ノンタイプテンプレートパラメータは、次のように記述します。

template <int SIZE>
class IntArray {
private:
	int  mArray[SIZE];
};

型のテンプレートパラメータの場合なら、typename(あるいは class)と書いていた部分に、具体的な型名を記述します。 こうすることで、テンプレート引数には、その型の定数値だけが指定できるようになります。

IntArray<100> iArray;         // 定数は OK

IntArray<50 + 50> iArray;     // 結果的に定数なので OK

int size = 100;
IntArray<size> iArray;        // 変数は使えない

const int ARRAY_SIZE = 100;
IntArray<ARRAY_SIZE> iArray;  // 定数は OK

enum { ARRAY_SIZE = 100; }
IntArray<ARRAY_SIZE> iArray;  // enum も定数なので OK

この例で、テンプレート引数に 100 が指定されたとすれば、テンプレートパラメータ SIZE の部分が 100 に置き換えられて、 次のようなクラスがインスタンス化されます。

template <int SIZE>
class IntArray {
private:
	int  mArray[100];  // SIZE == 100
};

なお、ノンタイプテンプレートパラメータには制約が幾つかあります。 具体的には、浮動小数点型、void型、クラス型、内部結合されるオブジェクトを指しているポインタは使えません。 4つ目の制約のため、文字列リテラルを使えないことに注意が必要です。


また、テンプレート引数が異なると、別の型がインスタンス化されることになります。 そのため、例えば、「IntArray<100>」と「IntArray<200>」は別の型であることに注意して下さい。 互いに関係性が無いので、一方の型の変数を、他方の型の変数に代入したり、互いを比較したりすることはできません。

テンプレートテンプレートパラメータ

次のコードを見てください。

template <typename DATA_STRUCT>
class IntDataStore {
public:
	/* 省略 */

private:
	DATA_STRUCT<int>  mValues;
};

意図としては、int型の複数の要素を管理するために使うデータ構造を、テンプレート引数で指定したいのです。 しかし、例えば、第20章で作った Stack を使って管理させるため、 次のように実体化しようとすると、エラーになります(実際には、Stackクラステンプレートにデフォルトコンストラクタが必要です)。

IntDataStore<Stack> store;  //エラー

問題は、テンプレートパラメータ DATA_STRUCT に対して、 更に <int> のようにテンプレート引数が指定される使い方をしている点です。 つまり、テンプレートパラメータ DATA_STRUCT に当てはめる具体的な型が、テンプレートになるような使い方は、 この章までの記述方法では表現できません。
そこで、テンプレートテンプレートパラメータという方法を使います。 これは、テンプレートパラメータがクラステンプレートであることを表現するものです。

template <template <typename T> class DATA_STRUCT>
class IntDataStore {
public:
	/* 省略 */

private:
	DATA_STRUCT<int>  mValues;
};

テンプレートテンプレートパラメータの記述「template <template <typename T> class DATA_STRUCT>」は、 かなり難解だと思うので、少し詳細に説明します。 以下の説明の中で「***」となっている箇所は、今は考えなくていいことを示しています。

まず「template <*** class DATA_STRUCT>」が、IntArrayクラステンプレートのテンプレートパラメータ DATA_STRUCT です。 ここで classキーワードが登場していることがポイントで、これは typenameキーワードではダメです

次に「*** template <typename T> ***」の部分ですが、これは DATA_STRUCT に当てはめられるクラステンプレートのテンプレートパラメータを表しています。 つまり、クラステンプレートを定義するときにいつも先頭に書いている「template <typename T>」のことです (だから、これに続く class は class でなければなりません)。 従って、もし DATA_STRUCT に当てはめられるクラステンプレートが、2つ以上のテンプレートパラメータを持っているなら、ここの記述も増えることになります。

ところで「*** template <typename T> ***」の中に登場する T は、どこからも使用されていません。 このように使われない名前は、省略してしまうことができます。 従って、「template <template <typename> class DATA_STRUCT>」と書いても構いません。

これで、先ほどのような Stack による実体化が可能になります。 プログラム全体を示すと、以下のようになります。

#include <iostream>

template <typename T>
class Stack {
public:
	explicit Stack(std::size_t capacity = 128);
	~Stack();
	
	void Push(const T& data);
	void Pop();
	inline const T Top() const
	{
		return mData[mSP - 1];
	}
	
	inline std::size_t GetSize() const
	{
		return mSP;
	}
	inline std::size_t GetCapacity() const
	{
		return mCapacity;
	}
	
private:
	const std::size_t      mCapacity;
	T*                     mData;
	int                    mSP;
};

template <typename T>
Stack<T>::Stack(std::size_t capacity) :
	mCapacity(capacity),
	mData(new T[capacity]),
	mSP(0)
{
}

template <typename T>
Stack<T>::~Stack()
{
	delete [] mData;
}

template <typename T>
void Stack<T>::Push(const T& data)
{
	assert(static_cast<std::size_t>(mSP) < mCapacity);
	mData[mSP] = data;
	mSP++;
}

template <typename T>
void Stack<T>::Pop()
{
	assert(mSP > 0);
	mSP--;
}


template <template <typename T> class DATA_STRUCT>
class IntDataStore {
public:
	/* 省略 */

private:
	DATA_STRUCT<int>  mValues;
};


int main()
{
	IntDataStore<Stack> store;
}

もし、Stack の代わりに、std::vector(【標準ライブラリ】第5章)を指定したとすると、コンパイルエラーになります。 これは、vector のテンプレートパラメータは2つあるため、IntDataStore のテンプレートテンプレートパラメータの数と一致しないからです。 この場合、IntDataStore のテンプレートテンプレートパラメータを「template <template <typename T, typename ALLOCATOR = std::allocator<T> > class DATA_STRUCT>」のように修正する必要があります。

C++11 (可変個テンプレートパラメータ)

C++11

C++11 から、テンプレートパラメータの個数を可変にできるようになりました。 これは、関数テンプレートでもクラステンプレートでも可能です。 関数テンプレートでの例については、すでに第9章で取り上げているので、そちらを参照して下さい。

ここでは例として、任意の個数、任意の型をもった値をまとめて取り扱うクラステンプレートを取り上げます。 ただし、この実装にはテンプレートの特殊化(第26章)を使っています。

#include <iostream>

template <typename... TYPES>
class Tuple {
};


template <typename FIRST, typename... TYPES>
class Tuple<FIRST, TYPES...> {
public:
	FIRST               value;
	Tuple<TYPES...>     children;
};


int main()
{
	Tuple<int>                       tuple0;
	Tuple<int, double>               tuple1;
	Tuple<int, double, const char*>  tuple2;

	tuple0.value = 10;
	tuple1.children.value = 3.5;
	tuple2.children.children.value = "abc";
}

同じ目的のクラステンプレート tuple が、標準ライブラリに含まれているので、通常はそちらを使って下さい。 このサンプルは、可変個テンプレートパラメータを持ったクラステンプレートの実装方法の例として、 最低限の内容を上げただけであり、ほとんど実用性はありません。

関数テンプレートの場合と同じで、テンプレートパラメータパック (...) が可変個引数を表しています。

また、再帰の手法を使って実装する点も同様です。 このクラステンプレートを使う際、1個以上の型をテンプレート引数で指定します。 結果、テンプレートパラメータFIRST、TYPES を持った方がインスタンス化されます。
1個目のテンプレート引数が、メンバ変数value の型となり、 2個目以降のテンプレート引数は、children の型(の一部)として使われます。 ここが再帰構造になっており、children は新たな Tuple を形成します。

VisualC++ 2013/2015/2017、clang 3.7 のいずれも対応しています。


練習問題

問題@ Stackクラステンプレートに、要素の上限数を指定するノンタイプテンプレートパラメータを追加し、 任意の型の要素を扱う固定サイズのスタックを作成して下さい。


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/12/20 「コンテナ」の項を、第23章へ移動。

'2015/11/15 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ