C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。
この章の概要です。
この章まで、テンプレート仮引数と言えば、次のように型を当てはめるものでした。
template <typename T> // テンプレート仮引数 T
class DataStore {
};
template <typename T1, typename T2> // テンプレート仮引数 T1、T2
void write_max(T1 a, T2 b);
<int> iStore;
DataStore<int, double>(5, 5.5); // 明示的な指定
write_max(5, 5.5); // 自動的に推測 write_max
クラステンプレート📘の場合は、テンプレート実引数は明示的に指定しなければなりませんが、関数テンプレート📘の場合は、関数の実引数から自動的に推測させることもできます(第9章)。
今度は、テンプレート仮引数が型でないパターンを取り上げます。このようなテンプレート仮引数は、ノンタイプテンプレート仮引数(非型テンプレート仮引数)と呼ばれます。
単に「テンプレート仮引数」と書いた場合、型であろうと型でなかろうと、クラステンプレートや関数テンプレートの宣言のところに書くパラメータのことを指しているか、型によるテンプレート仮引数のことを指しているかのどちらかになります。分かりづらいですが、文脈から区別は付くと思います。
ノンタイプテンプレート仮引数は、次のように記述します。
template <int SIZE>
class IntArray {
private:
int mArray[SIZE];
};
型のテンプレート仮引数の場合なら、typename(あるいは class)と書いていた部分に、具体的な型名を記述します。テンプレート実引数には、その型の定数値だけが指定できます。
<100> iArray; // 定数は OK
IntArray
<50 + 50> iArray; // 結果的に定数なので OK
IntArray
int size = 100;
<size> iArray; // 変数は使えない
IntArray
const int ARRAY_SIZE = 100;
<ARRAY_SIZE> iArray; // 定数は OK
IntArray
enum { ARRAY_SIZE = 100; }
<ARRAY_SIZE> iArray; // enum も定数なので OK IntArray
この例で、テンプレート実引数に 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:
<int> mValues;
DATA_STRUCT};
意図としては、int型の複数の要素を管理するために使うデータ構造📘を、テンプレート実引数で指定したいのです。しかし、たとえば、第20章で作った Stack を使って管理させるため、次のように実体化しようとすると、エラーになります(実際には、Stackクラステンプレートにデフォルトコンストラクタが必要です)。
<Stack> store; //エラー IntDataStore
問題は、テンプレート仮引数 DATA_STRUCT に対して、さらに <int> のようにテンプレート実引数が指定される使い方をしている点です。つまり、テンプレート仮引数 DATA_STRUCT に当てはめる具体的な型が、テンプレートになるような使い方は、この章までの記述方法では表現できません。
そこで、テンプレートテンプレート仮引数という方法を使います。これは、テンプレート仮引数がクラステンプレートであることを表現するものです。
template <template <typename T> class DATA_STRUCT>
class IntDataStore {
public:
/* 省略 */
private:
<int> mValues;
DATA_STRUCT};
テンプレートテンプレート仮引数の記述「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;
* mData;
Tint mSP;
};
template <typename T>
<T>::Stack(std::size_t capacity) :
Stack(capacity),
mCapacity(new T[capacity]),
mData(0)
mSP{
}
template <typename T>
<T>::~Stack()
Stack{
delete [] mData;
}
template <typename T>
void Stack<T>::Push(const T& data)
{
assert(static_cast<std::size_t>(mSP) < mCapacity);
[mSP] = data;
mData++;
mSP}
template <typename T>
void Stack<T>::Pop()
{
assert(mSP > 0);
--;
mSP}
template <template <typename T> class DATA_STRUCT>
class IntDataStore {
public:
/* 省略 */
private:
<int> mValues;
DATA_STRUCT};
int main()
{
<Stack> store;
IntDataStore}
【上級】もし、Stack の代わりに、std::vector(【標準ライブラリ】第5章)を指定したとすると、コンパイルエラーになります。これは、vector のテンプレート仮引数は2つあるため、IntDataStore のテンプレートテンプレート仮引数の数と一致しないからです。この場合、IntDataStore のテンプレートテンプレート仮引数を「template <template <typename T, typename ALLOCATOR = std::allocator<T> > class DATA_STRUCT>」のように修正する必要があります。
C++11 から、テンプレート仮引数の個数を可変にできるようになりました。これは、関数テンプレートでもクラステンプレートでも可能です。関数テンプレートでの例については、すでに第9章で取り上げているので、そちらを参照してください。
ここでは例として、任意の個数、任意の型をもった値をまとめて取り扱うクラステンプレートを取り上げます。ただし、この実装にはテンプレートの特殊化(第26章)を使っています。
#include <iostream>
template <typename... TYPES>
class Tuple {
};
template <typename FIRST, typename... TYPES>
class Tuple<FIRST, TYPES...> {
public:
;
FIRST value<TYPES...> children;
Tuple};
int main()
{
<int> tuple0;
Tuple<int, double> tuple1;
Tuple<int, double, const char*> tuple2;
Tuple
.value = 10;
tuple0.children.value = 3.5;
tuple1.children.children.value = "abc";
tuple2}
同じ目的のクラステンプレート tuple が、標準ライブラリに含まれているので、通常はそちらを使ってください。このサンプルは、可変個テンプレート仮引数を持ったクラステンプレートの実装方法の例として、最低限の内容を上げただけであり、ほとんど実用性はありません。
関数テンプレートの場合と同じで、テンプレート仮引数パック (…) が可変個引数を表しています。
また、再帰の手法を使って実装する点も同様です。このクラステンプレートを使う際、1個以上の型をテンプレート実引数で指定します。結果、テンプレート仮引数FIRST、TYPES を持った方がインスタンス化されます。
1個目のテンプレート実引数が、メンバ変数value の型となり、2個目以降のテンプレート実引数は、children の型(の一部)として使われます。ここが再帰構造になっており、children は新たな Tuple を形成します。
問題① Stackクラステンプレートに、要素の上限数を指定するノンタイプテンプレート仮引数を追加し、 任意の型の要素を扱う固定サイズのスタックを作成してください。
Programming Place Plus のトップページへ
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
![]() |
管理者情報 | プライバシーポリシー |