テンプレートは、 C++プログラミング言語の機能の一つで、関数やクラスが汎用型を扱うことを可能にします。これにより、関数やクラスの宣言において、汎用変数を介して別のクラス(組み込み型または新規に宣言されたデータ型)を参照することができ、それぞれのクラスに対して完全な宣言を作成する必要がなくなります。
簡単に言うと、テンプレート化されたクラスや関数は、(コンパイル前に)テンプレート化されたコードブロックをコピーして、それが使用されている場所に貼り付け、テンプレートパラメータを実際のパラメータに置き換えることと同等です。このため、テンプレート化されたメソッドを使用するクラスでは、実装をヘッダーファイル(*.hファイル)に配置します。これは、型を事前に知らなければシンボルをコンパイルできないためです。
C ++標準ライブラリは、連結されたテンプレートの枠組みの中で、多くの便利な関数を提供します。
C++テンプレートの主なインスピレーションは、言語CLUが提供するパラメータ化モジュールとAdaが提供するジェネリクスであった。[ 1 ]
テンプレートには、関数テンプレート、クラステンプレート、そしてC++14以降では変数テンプレートの3種類があります。C ++11以降では、テンプレートは可変引数型と非可変引数型の両方に対応していますが、それ以前のバージョンのC++では常に非可変引数型でした。
関数テンプレートは、さまざまな型の引数を受け入れることができる点を除けば、関数と同様に動作します。これにより、型に依存しない動作が可能になります(例を参照)。言い換えれば、関数テンプレートは関数のファミリーを表します。型パラメータを持つ関数テンプレートを宣言する形式は次のとおりです。
テンプレート< class Identifier >宣言;テンプレート< typename Identifier >宣言;どちらの表現も同じ意味を持ち、動作も全く同じです。後者の形式は混乱を避けるために導入されました[ 3 ]。intなぜなら、C++20までは型パラメータはクラスである必要がないからです(やのような基本型でも構いませんdouble)。
例えば、C++標準ライブラリには、とmax(x, y)の大きい方を返す関数テンプレートが含まれています。その関数テンプレートは次のように定義できます[ 4 ]:xy
template < typename T > [[ nodiscard ]] constexpr T & max ( const T & a , const T & b ) noexcept { return a < b ? b : a ; }この単一の関数定義は、多くのデータ型に対応します。具体的には、<(小なり演算子)が定義されているすべてのデータ型に対応し、型が変換可能な値を返しますbool。関数テンプレートを使用することで、ソースコードファイルのスペースを節約できるだけでなく、変更箇所を1つの関数記述に限定し、コードを読みやすくすることができます。
インスタンス化された関数テンプレートは、特定のプログラムで使用されるさまざまなデータ型ごとに個別の関数を作成する場合と比べて、通常は同じオブジェクトコードを生成します。たとえば、プログラムが上記の関数テンプレートの とintの両方のdoubleバージョンを使用する場合、コンパイラは引数を操作するのオブジェクトコードバージョンと、引数を操作する別のオブジェクトコードバージョンを作成します。コンパイラの出力は、ソースコードに を処理するように記述された と、 を処理するように記述された の 2 つの別々の非テンプレートバージョンが含まれていた場合に生成されるものと同一になります。max()max()intdoublemax()intdouble
関数テンプレートは以下のように使用できます。
import std ;int main () { // これは暗黙の引数推論によって max<int> を呼び出します。std :: println ( "{}" , std :: max ( 3 , 7 ));// これは暗黙の引数推論によって max<double> を呼び出します。std :: println ( "{}" , std :: max ( 3.0 , 7.0 ));//引数の型を明示的に指定する必要があります。//ただし、std::type_identity でこの問題を解決できます... std :: println ( "{}" , max <double> ( 3 , 7.0 )); }最初の 2 つのケースでは、テンプレート引数Tはコンパイラによって自動的にintとに推論されますdouble。3 番目のケースでは、max(3, 7.0)パラメータの型は一般的にテンプレート引数と完全に一致する必要があるため、 の自動推論は失敗します。したがって、 を明示的にインスタンス化しdoubleますmax<double>()。
この関数テンプレートは、式が有効なコピー構築可能な型であれば、どれでもインスタンス化できます。ユーザー定義型の場合、これは型内で小なり演算子()がオーバーロードされている必要があることを意味します。y < x<
C++20以降、関数宣言のいずれかのパラメータでautoまたはを使用すると、その宣言は省略された関数テンプレート宣言になります。[ 5 ]このような宣言は関数テンプレートを宣言し、各プレースホルダに対して1つの発明されたテンプレートパラメータがテンプレートパラメータリストに追加されます。concept auto
// 以下と同等: // template <typename T> // void f1(T x); void f1 ( auto x );// (Concept1 が概念の場合)以下と同等です。// <Concept1 T> // void f2(T x); void f2 ( Concept1 auto x );// (Concept2 がコンセプトの場合)と同等です。// テンプレート <Concept2... Ts> // void f3(Ts... xs) void f3 ( Concept2 auto ... xs );// (Concept2 が概念の場合)以下と同等です。// テンプレート <Concept2 T> // void f4(T... xs); void f4 ( Concept2 auto xs , ...);// (Concept3 と Concept4 が概念の場合)と同等です。// template <Concept3 T, Concept4 U> // f5(const T* t, U& u); void f5 ( const Concept3 auto * t , Concept4 auto & u );使用する概念を制約する方法としてはmax()、次のようなものが考えられます。
std :: totally_orderedを使用します。// 型名の宣言内: template < totally_ordered T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }// requires句内: template < typename T > requires totally_ordered < T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }クラステンプレートは、パラメータに基づいてクラスを生成するための仕様を提供します。クラステンプレートは一般的にコンテナを実装するために使用されます。クラステンプレートは、指定された型のセットをテンプレート引数として渡すことによってインスタンス化されます。[ 6 ] C++ 標準ライブラリには、多くのクラステンプレート、特に標準テンプレートライブラリから採用されたコンテナ(など)が含まれていますvector。
C++14では、次の例のように、テンプレートを変数にも使用できます。
template < typename T > constexpr T PI = T { 3.141592653589793238462643383L }; // (ほぼ)std::numbers::pi から上記の例のように、型に対するテンプレート化はC++で最も一般的なテンプレート化の形式ですが、値に対するテンプレート化も可能です。例えば、次のように宣言されたクラスは
テンプレート< int K >クラスMyClass ;特定のインスタンス化が可能ですint。
実際の例として、標準ライブラリの固定サイズ配列型はstd::array、型(配列が保持するオブジェクトの型を表す)と、型(配列が保持する要素の数を表す)の数値の両方に基づいてテンプレート化されています。と同等のstd::size_tクラスを作成するには、次のように宣言できます。Arraystd::array
template < class T , size_t N > struct Array ;そして、6つの配列charを宣言することができます。
配列< char , 6 > myArray ;関数やクラスがテンプレートからインスタンス化される場合、コンパイラは使用される引数のセットに対してそのテンプレートの特殊化を作成し、その特殊化は生成された特殊化と呼ばれます。
プログラマーは、特定のテンプレート型引数セットに対して、関数(またはクラス)の特別なバージョンを実装することを決定する場合があります。これは明示的な特殊化と呼ばれます。このようにして、特定のテンプレート型は、その型に最適化された、あるいは汎用的な実装よりも意味のある、特殊な実装を持つことができます。
明示的な特殊化は、テンプレートパラメータの特定の選択に対する関数またはクラスの動作が、一般的な動作、つまりメインテンプレートまたは複数のテンプレートによって生成されるコードから逸脱する必要がある場合に使用されます。たとえば、以下のテンプレート定義は、max()型の引数に対する特定の実装を定義しますconst char*。
import std ;template <> [[ nodiscard ]] constexpr const char * max ( const char * a , const char * b ) noexcept { // 通常、 2 つの C 文字列を直接比較した結果は未定義の動作になります。 // std::strcmp を使用すると定義されます。return std :: strcmp ( a , b ) > 0 ? a : b ; }C++11 では可変引数テンプレートが導入され、可変引数関数と似た方法で可変個の引数を取ることができますstd::printf。
using std :: format_string ; using std :: ofstream ;enum class Level { ... };ofstream logFile { "logfile.txt" };template < typename ... Args > void log ( const format_string < Args ... >& fmt , Args && ... args ) { std :: println ( logFile , fmt , args ...); }C++ではCスタイルの可変引数しかサポートされていないため、Javaのように型安全な可変引数関数を実現するには、可変引数テンプレートを使用するしか方法がありません。
C++11では、パラメータ化されたtypedefのように機能するテンプレートエイリアスが導入されました。
以下のコードはstd::map、 を にTreeMap、std::unordered_mapを に名前変更するHashMapとともに、 のエイリアスを作成する方法StringHashMapを示していますstd::unordered_map<K, std::string>。これにより、例えばStringHashMap<int>を の省略形として使用できるようになりますstd::unordered_map<int, std::string>。
using String = std :: string ;// ハッシュ関数、アロケータなどのオプションの特殊化を可能にする。template < typename K , typename V , typename Compare = std :: less < K > , typename Alloc = std :: allocator < std :: pair < const K , T >> > using TreeMap = std :: map < K , V , Compare , Alloc > ;template < typename K , typename V , typename HashFn = std :: hash < K > , typename KeyEq = std :: equal_to < K > , typename Alloc = std :: allocator < std :: pair < const K , T >> > using HashMap = std :: unordered_map < K , V , HashFn , KeyEq , Alloc > ;// または、K と V のみを特殊化できるようにする: template < typename K , typename V > using TreeMap = std :: map < K , V > ;template < typename K , typename V > using HashMap = std :: unordered_map < K , V > ;// StringHashMap<K> = HashMap<K, String> を定義しますtemplate < typename K > using StringHashMap = HashMap < K , String > ;StringHashMap < int > myMap = /* ここに何か... */ ;C++20以降、テンプレートはJavaのジェネリクスワイルドカードやC#およびRustの句と同様に制約できるようになりました。これは、コンパイル時に評価される一連のブール述語を表す概念を使用して行われます。where
例えば、このコードは をstd::derived_from<Derived, Based>継承の上限として使用します。クラスが から継承する場合Player、この概念を満たし、継承しないクラスは のテンプレート パラメータとして使用できませんprocessListOfPlayers()。
import std ;using std :: derived_from ; using std :: vector ;class Player { // ... };// T は、継承の上限が Player である型である必要があります。// Player を継承しない型はすべてブロックされます。template < derived_from < Player > T > void processListOfPlayers ( const vector < T >& players ) { // ... }概念は、型安全な可変引数の形式を実装するために使用できます。たとえば、Java シグネチャ<T>voidfn(T...args);
std :: same_asを使用します。template < typename T > void fn ( same_as < T > auto ... args ) { // ... }さらに、これらを特定の型に制限するために、およびの概念を使用できます。たとえば、Javaのシグネチャおよびは、それぞれおよびと記述できます。std::same_as<T,U>std::convertible_to<T,U>voidfoo(int...args);voidbar(String...args);voidfoo(same_as<int>auto...args);voidbar(convertible_to<string>auto...args);
C++03では、「エクスポートされたテンプレート」がC++に追加されました。[ 7 ]しかし、この機能を実際にサポートするコンパイラが非常に少なかったため、C++11で削除されました。 [ 8 ]エクスポートされたテンプレートをサポートしていたことが知られている唯一のコンパイラは、 Comeau C/C++でした。削除の理由として挙げられたものには、以下のようなものがあります。
「エクスポートされたテンプレート」とは、静的データメンバーと非インラインメソッドがエクスポートされるクラステンプレートのことです。キーワードでマークする必要があります。「エクスポートされたテンプレート」の特徴は、テンプレートを使用する翻訳単位exportで定義する必要がないことです。 [ 9 ] [ 10 ]例えば(C++03の場合):
ファイル1.cpp :
#include <iostream>static void trace () { std :: cout << "ファイル 1" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y );int main () { trace (); std :: cout << min ( 2 , 3 ) << std :: endl ; }ファイル2.cpp :
#include <iostream>static void trace () { std :: cout << "ファイル2" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y ) { trace (); return a < b ? a : b ; }C++20でモジュールが導入されたことで、キーワードがC++に再び追加されました。これにより、次のような宣言が再び可能になりました。export
import std ;std :: derived_from を使用します。export class Atom { // ... };export template < derived_from < Atom > Instance , typename ... Bases > class Cluster : public Bases ... { private : Instance x ; public : explicit Cluster ( Instance x , Bases && ... bases ) : Bases ( bases )..., x { x } {}// ... };エクスポートされたテンプレートによって提供されるはずだったコンパイル速度の向上という利点は、モジュールによって既に実現されているため、この機能は実質的に時代遅れとなり、モジュールに取って代わられている。
当初、 JavaやC# 1.0などの一部の言語にはテンプレートの概念は含まれていませんでした。Javaのジェネリクスはテンプレートの動作を模倣していますが、技術的には異なります。C#は.NET 2.0でジェネリクス(パラメータ化された型)を追加しました。AdaのジェネリクスはC++のテンプレートよりも前から存在していました。
C++ テンプレート、Java ジェネリクス、および.NETジェネリクスは類似していると見なされることが多いが、ジェネリクスはC++テンプレートの基本的な動作を模倣するにすぎない。[ 11 ] BoostやSTLSoftなどのライブラリや STL の実装で使用されている高度なテンプレート機能の一部(明示的または部分的な特殊化、デフォルトのテンプレート引数、テンプレート非型引数、テンプレートテンプレート引数など) は、ジェネリクスでは利用できない。
C++ テンプレートでは、コンパイル時のケースは従来、テンプレート引数に対するパターンマッチングによって処理されていました。たとえば、以下の Factorial の例のテンプレート基底クラスは、以前は利用できなかった不等式テストではなく、0 にマッチすることで実装されています。しかし、C++11 で標準ライブラリ機能などが導入されたことで、std::conditional条件付きテンプレートのインスタンス化を処理するための、より柔軟な方法が提供されるようになりました。
// 誘導テンプレート< unsigned int N > struct Factorial { static constexpr unsigned int value = N * Factorial < N - 1 >:: value ; };// テンプレート特殊化による基本ケース: template <> struct Factorial < 0 > { static constexpr unsigned int value = 1 ; };これらの定義を用いると、例えば 6! をコンパイル時に式 を使って計算することができるFactorial<6>::value。
あるいは、constexprC++11 / if constexprC++17 では、コンパイル時に関数を使用してこれらの値を直接計算することができます。
template < unsigned int N > [[ nodiscard ]] constexpr unsigned int factorial () noexcept { if constexpr ( N <= 1 ) { return 1 ; } else { return N * factorial < N - 1 > (); } }このため、テンプレートメタプログラミングは現在、主に型に対する操作を行うために用いられている。