テンプレート (C++)

テンプレートは、C++ での汎用プログラミングの基礎となります。 厳密に型指定された言語として、C++ では、すべての変数に特定の型を割り当てる必要があります。これは、プログラマが明示的に宣言するか、コンパイラが推測するかのいずれかです。 ただし、操作する型に関係なく、多くのデータ構造とアルゴリズムは同じように見えます。 テンプレートを使用すると、クラスまたは関数の操作を定義し、その操作が具体的にどの型で動作するかをユーザーが指定できるようになります。

テンプレートの作成と使用

テンプレートは、ユーザーがテンプレート パラメーターに指定した引数に基づいて、コンパイル時に通常の型または関数を生成するコンストラクトです。 たとえば、次のような関数テンプレートを定義できます。

template <typename T>
T minimum(const T& lhs, const T& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

上記のコードでは、1 つの型パラメーター T を持つ汎用関数のテンプレートについて説明します。このテンプレートの戻り値と呼び出しパラメーター (lhs と rhs) はすべてこの型です。 型パラメーターには任意の名前を指定できますが、規則により、大文字 1 文字が最もよく使用されます。 T はテンプレート パラメーターです。typename キーワードは、このパラメーターが型のプレースホルダーであることを示します。 関数が呼び出されると、コンパイラは、T のすべてのインスタンスを、ユーザーによって指定されるか、コンパイラによって推測される具体的な型引数に置き換えます。 コンパイラがテンプレートからクラスまたは関数を生成するプロセスを、テンプレートのインスタンス化{2}と呼びますminimum<int> は、テンプレート minimum<T> のインスタンス化です。

他の場所では、ユーザーは、int に特化したテンプレートのインスタンスを宣言できます。get_a() および get_b() は int を返す関数であるとします。

int a = get_a();
int b = get_b();
int i = minimum<int>(a, b);

ただし、これは関数テンプレートで、コンパイラは引数 a および b から T の型を推測できるため、通常の関数のように呼び出すことができます。

int i = minimum(a, b);

コンパイラは、最後のステートメントを検出すると、テンプレート内の T のすべての出現箇所が int に置き換えられる新しい関数を生成します。

int minimum(const int& lhs, const int& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

コンパイラが関数テンプレートで型推論を実行する方法に関する規則は、通常の関数の規則に基づいています。 詳細については、「関数テンプレート呼び出しのオーバーロード解決」をご覧ください。

型パラメーター

上記の minimum テンプレートでは、型パラメーター T は、const および参照修飾子が追加される関数呼び出しパラメーターで使用されるまで、どのような方法でも修飾されていないことに注意してください。

型パラメーターの数には事実上制限はありません。 複数のパラメーターをコンマで区切ります。

template <typename T, typename U, typename V> class Foo{};

キーワード class はこのコンテキストでは typename に相当します。 前の例を次のように表すことができます。

template <class T, class U, class V> class Foo{};

省略記号演算子 (...) を使用すると、任意の数の 0 個以上の型パラメーターを受け取るテンプレートを定義できます。

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;

任意の組み込みまたはユーザー定義型を型引数として使用できます。 たとえば、標準ライブラリで std::vector を使用して、型 intdoublestd::stringMyClassconstMyClass*、MyClass&、などの変数を格納できます。 テンプレートを使用する場合の主な制限は、型引数が型パラメーターに適用されるすべての操作をサポートする必要があることです。 たとえば、次の例のように MyClass を使用して minimum を呼び出したとします。

class MyClass
{
public:
    int num;
    std::wstring description;
};

int main()
{
    MyClass mc1 {1, L"hello"};
    MyClass mc2 {2, L"goodbye"};
    auto result = minimum(mc1, mc2); // Error! C2678
}

MyClass< 演算子のオーバーロードを提供しないため、コンパイラ エラーが生成されます。

特定のテンプレートの型引数がすべて同じオブジェクト階層に属しているという固有の要件はありませんが、このような制限を適用するテンプレートを定義することはできます。 オブジェクト指向の手法をテンプレートと組み合わせることができます。たとえば、ベクター <Base*> で Derived* を格納できます。 引数はポインターである必要があることに注意してください

vector<MyClass*> vec;
   MyDerived d(3, L"back again", time(0));
   vec.push_back(&d);

   // or more realistically:
   vector<shared_ptr<MyClass>> vec2;
   vec2.push_back(make_shared<MyDerived>());

std::vector とその他の標準ライブラリ コンテナーが T の要素に課す基本的な要件は、T が copy-assignable および copy-constructible である必要があるということです。

非型パラメーター

C# や Java などの他の言語のジェネリック型とは異なり、C++ テンプレートでは、値パラメーターとも呼ばれる 非型パラメーター がサポートされています。 たとえば、標準ライブラリの std::array クラスに似ている次の例のように、定数整数値を指定して、配列の長さを指定できます。

template<typename T, size_t L>
class MyArray
{
    T arr[L];
public:
    MyArray() { ... }
};

テンプレート宣言の構文に注意してください。 size_t 値は、コンパイル時にテンプレート引数として渡され、これは const 式または constexpr 式である必要があります。 これは次のように使用します。

MyArray<MyClass*, 10> arr;

ポインターや参照を含むその他の種類の値は、非型パラメーターとして渡すことができます。 たとえば、関数または関数オブジェクトへのポインターを渡して、テンプレート コード内の一部の操作をカスタマイズできます。

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

Visual Studio 2017 以降、/std:c++17 モード以降では、コンパイラは auto で宣言されている非型テンプレート引数の型を推測します。

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

テンプレート パラメーターとしてのテンプレート

テンプレートは、テンプレート パラメーターにすることができます。 この例では、MyClass2 には、typename パラメーター T とテンプレート パラメーター Arr の 2 つのテンプレート パラメーターがあります。

template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
    U u; //Error. U not in scope
};

Arr パラメーター自体には本体がないため、パラメーター名は必要ありません。 実際には、MyClass2 の本体内から Arr の typename またはクラス パラメーター名を参照するとエラーになります。 このため、次の例に示すように、Arr の型パラメーター名を省略できます。

template<typename T, template<typename, int> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
};

既定のテンプレート引数

クラス テンプレートと関数テンプレートには、既定の引数を指定できます。 テンプレートに既定の引数がある場合は、使用時にそのままにしておくことができます。 たとえば、std:: vector テンプレートには、アロケーターの既定の引数があります。

template <class T, class Allocator = allocator<T>> class vector;

ほとんどの場合、既定の std::allocator クラスが許容されるため、次のようなベクターを使用します。

vector<int> myInts;

ただし、必要に応じて、次のようにカスタム アロケーターを指定できます。

vector<int, MyAllocator> ints;

テンプレート引数が複数ある場合、最初の既定の引数の後は、すべての引数に既定の引数が必要です。

パラメーターがすべて既定値であるテンプレートを使用する場合は、空の山かっこを使用します。

template<typename A = int, typename B = double>
class Bar
{
    //...
};
...
int main()
{
    Bar<> bar; // use all default type arguments
}

テンプレートの特殊化

場合によっては、テンプレートが任意の型に対してまったく同じコードを定義することは不可能または望ましい場合があります。 たとえば、型引数がポインター、std:: wstring、または特定の基底クラスから派生した型である場合にのみ実行されるコード パスを定義できます。 そのような場合は、特定の型のテンプレートの 特殊化 を定義できます。 ユーザーがその型を使用してテンプレートをインスタンス化すると、コンパイラは特殊化を使用してクラスを生成し、それ以外のすべての型の場合、コンパイラがより一般的なテンプレートを選択します。 すべてのパラメーターが特殊化されている特殊化は 完全な特殊化 です。 一部のパラメーターのみが特殊化されている場合は、部分的特殊化 と呼ばれます。

template <typename K, typename V>
class MyMap{/*...*/};

// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization

テンプレートは、特化された各型パラメーターが一意である限り、任意の数の特殊化を持つことができます。 クラス テンプレートのみが部分的に特殊化されている場合があります。 テンプレートのすべての完全な特殊化と部分的特殊化は、元のテンプレートと同じ名前空間で宣言する必要があります。

詳細については、「テンプレートの特殊化」をご覧ください。