テンプレート仮引数 | Programming Place Plus C++編【言語解説】 第22章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 と更新されており、今後も 3年ごとに更新されます。
なかでも 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);


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 を形成します。


練習問題

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


解答ページはこちら

参考リンク


更新履歴

’2018/1/5 コンパイラの対応状況について、対応している場合は明記しない方針にした。

’2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

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

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

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

’2015/11/15 新規作成。



前の章へ (第21章 テンプレートのインスタンス化)

次の章へ (第23章 テンプレートの特殊化)

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る