サーチ…
前書き
クラス、関数、および(C ++ 14以降の)変数はテンプレート化することができます。テンプレートは、すべてのパラメータが指定されたときに具体的なクラス、関数、または変数になるいくつかの空きパラメータを持つコードです。パラメータには型、値、またはテンプレートを指定できます。よく知られているテンプレートはstd::vector
であり、要素の型が指定されたときに具体的なコンテナ型になります。 例えば、 std::vector<int>
です。
構文
- template < template-parameter-list > 宣言
- テンプレートをエクスポートする< template-parameter-list > 宣言 / * C ++ 11まで* /
- テンプレート<> 宣言
- テンプレート宣言
- externテンプレート宣言 / * C ++ 11以降* /
- テンプレート< template-parameter-list >クラス...( opt ) 識別子 ( opt )
- テンプレート< template-parameter-list >クラス識別子 ( opt )= id-expression
- テンプレート< template-parameter-list > typename ...( opt ) 識別子 ( opt )/ * C ++以降17 * /
- テンプレート< template-parameter-list >型名識別子 ( opt )= id-expression / * C ++以降17 * /
- postfix-expression 。テンプレートID-expression
- postfix-expression - >テンプレートid-expression
- ネストされた名前指定子の
template
simple-template-id::
備考
単語template
は、コンテキストに応じて、C ++言語で5つの異なる意味を持つキーワードです。
<>
で囲まれたテンプレートパラメータのリストの後には、 クラステンプレート 、 関数テンプレート 、または既存のテンプレートの部分的な特殊化などのテンプレートが宣言されます。template <class T> void increment(T& x) { ++x; }
その後に空の
<>
があると、 明示的な(完全な)特殊化が宣言されます。template <class T> void print(T x); template <> // <-- keyword used in this sense here void print(const char* s) { // output the content of the string printf("%s\n", s); }
その後に
<>
ない宣言が続くと、 明示的なインスタンス化の宣言または定義を形成します 。template <class T> std::set<T> make_singleton(T x) { return std::set<T>(x); } template std::set<int> make_singleton(int x); // <-- keyword used in this sense here
テンプレートパラメータリスト内には、テンプレートテンプレートパラメータが導入されています 。
template <class T, template <class U> class Alloc> // ^^^^^^^^ keyword used in this sense here class List { struct Node { T value; Node* next; }; Alloc<Node> allocator; Node* allocate_node() { return allocator.allocate(sizeof(T)); } // ... };
スコープ解決演算子
::
およびクラスメンバーアクセス演算子の後.
および->
場合、次の名前がテンプレートであることを指定します。struct Allocator { template <class T> T* allocate(); }; template <class T, class Alloc> class List { struct Node { T value; Node* next; } Alloc allocator; Node* allocate_node() { // return allocator.allocate<Node>(); // error: < and > are interpreted as // comparison operators return allocator.template allocate<Node>(); // ok; allocate is a template // ^^^^^^^^ keyword used in this sense here } };
C ++ 11の前に、テンプレートを使用して宣言することができexport
キーワード エクスポートテンプレートにそれを作ります、。エクスポートされたテンプレートの定義は、テンプレートがインスタンス化されるすべての翻訳単位に存在する必要はありません。たとえば、次のように動作するはずです。
foo.h
:
#ifndef FOO_H
#define FOO_H
export template <class T> T identity(T x);
#endif
foo.cpp
:
#include "foo.h"
template <class T> T identity(T x) { return x; }
main.cpp
:
#include "foo.h"
int main() {
const int x = identity(42); // x is 42
}
実装の難しさから、 export
キーワードはほとんどの主要なコンパイラでサポートされていませんでした。これはC ++ 11で削除されました。今、 export
キーワードをまったく使用することは違法です。その代わりに、通常はヘッダーにテンプレートを定義する必要があります(通常、ヘッダーに定義されていないテンプレート以外の関数とは異なります)。 ヘッダーファイルにテンプレートのみを実装できる理由を参照してください。
機能テンプレート
テンプレートは、同じ効果を持つ関数(より伝統的な構造と同様に)にも適用できます。
// 'T' stands for the unknown type
// Both of our arguments will be of the same type.
template<typename T>
void printSum(T add1, T add2)
{
std::cout << (add1 + add2) << std::endl;
}
これは構造テンプレートと同じ方法で使用できます。
printSum<int>(4, 5);
printSum<float>(4.5f, 8.9f);
どちらの場合も、テンプレートの引数はパラメータの型を置き換えるために使用されます。結果は通常のC ++関数と同じように機能します(パラメータがコンパイラが標準の変換を適用するテンプレートの型と一致しない場合)。
テンプレート関数のもう1つの特性(テンプレートクラスとは異なり)は、関数に渡されるパラメータに基づいてテンプレートパラメータを推論できるということです。
printSum(4, 5); // Both parameters are int.
// This allows the compiler deduce that the type
// T is also int.
printSum(5.0, 4); // In this case the parameters are two different types.
// The compiler is unable to deduce the type of T
// because there are contradictions. As a result
// this is a compile time error.
この機能により、テンプレート構造と関数を組み合わせるときにコードを単純化することができます。標準ライブラリには、ヘルパー関数make_X()
を使ってtemplate structure X
を作るための共通のパターンがあります。
// The make_X pattern looks like this.
// 1) A template structure with 1 or more template types.
template<typename T1, typename T2>
struct MyPair
{
T1 first;
T2 second;
};
// 2) A make function that has a parameter type for
// each template parameter in the template structure.
template<typename T1, typename T2>
MyPair<T1, T2> make_MyPair(T1 t1, T2 t2)
{
return MyPair<T1, T2>{t1, t2};
}
これはどのように役立ちますか?
auto val1 = MyPair<int, float>{5, 8.7}; // Create object explicitly defining the types
auto val2 = make_MyPair(5, 8.7); // Create object using the types of the paramters.
// In this code both val1 and val2 are the same
// type.
注:これはコードを短縮するためのものではありません。これは、コードをより堅牢にするために設計されています。複数の場所ではなく、単一の場所でコードを変更することで、型を変更することができます。
引数転送
Templateは、 転送参照を使用して左辺値と右辺値の両方の参照を受け入れることができます 。
template <typename T>
void f(T &&t);
この場合、実際のタイプのt
はコンテキストに応じて導出されます。
struct X { };
X x;
f(x); // calls f<X&>(x)
f(X()); // calls f<X>(x)
最初のケースでは、タイプT
はX
( X&
) への参照として導出され、 t
のタイプはX
への左値参照であり、第2のケースではT
のタイプがX
として導出され、 t
値参照X
( X&&
)。
注意:最初のケースでは、 decltype(t)
はT
と同じであるが、2番目のケースではないことに注意することは重要です。
t
を別の関数に完全に転送するには、左辺値でも左値でも、 std::forward
:を使う必要があります。
template <typename T>
void f(T &&t) {
g(std::forward<T>(t));
}
フォワーディングリファレンスはバリデーショナルテンプレートで使用できます:
template <typename... Args>
void f(Args&&... args) {
g(std::forward<Args>(args)...);
}
注:転送参照はテンプレートパラメータに対してのみ使用できます。たとえば、次のコードではv
は転送参照ではなくrvalue参照です。
#include <vector>
template <typename T>
void f(std::vector<T> &&v);
基本クラステンプレート
クラステンプレートの基本的な考え方は、テンプレートパラメータがコンパイル時に型で置き換えられることです。その結果、同じクラスを複数の型に対して再利用することができます。ユーザーは、クラスの変数が宣言されたときに使用される型を指定します。これの3つの例がmain()
示されています:
#include <iostream>
using std::cout;
template <typename T> // A simple class to hold one number of any type
class Number {
public:
void setNum(T n); // Sets the class field to the given number
T plus1() const; // returns class field's "follower"
private:
T num; // Class field
};
template <typename T> // Set the class field to the given number
void Number<T>::setNum(T n) {
num = n;
}
template <typename T> // returns class field's "follower"
T Number<T>::plus1() const {
return num + 1;
}
int main() {
Number<int> anInt; // Test with an integer (int replaces T in the class)
anInt.setNum(1);
cout << "My integer + 1 is " << anInt.plus1() << "\n"; // Prints 2
Number<double> aDouble; // Test with a double
aDouble.setNum(3.1415926535897);
cout << "My double + 1 is " << aDouble.plus1() << "\n"; // Prints 4.14159
Number<float> aFloat; // Test with a float
aFloat.setNum(1.4);
cout << "My float + 1 is " << aFloat.plus1() << "\n"; // Prints 2.4
return 0; // Successful completion
}
テンプレートの特殊化
テンプレートクラス/メソッドの特定のインスタンス化の実装を定義できます。
たとえば、次のような場合:
template <typename T>
T sqrt(T t) { /* Some generic implementation */ }
あなたは次のように書くことができます:
template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }
sqrt(4.0)
を書くユーザはジェネリックな実装を得ますが、 sqrt(4)
は特別な実装を得ます。
部分テンプレートの特殊化
完全なテンプレートの特殊化とは対照的に、部分的なテンプレートの特殊化により、既存のテンプレートの引数のいくつかを修正したテンプレートを導入することができます。部分的なテンプレートの特殊化は、テンプレートクラス/構造体に対してのみ使用できます。
// Common case:
template<typename T, typename U>
struct S {
T t_val;
U u_val;
};
// Special case when the first template argument is fixed to int
template<typename V>
struct S<int, V> {
double another_value;
int foo(double arg) {// Do something}
};
上記のように、部分テンプレートの特殊化は、データと関数のメンバーの完全に異なるセットを導入することがあります。
部分的に特殊化されたテンプレートがインスタンス化されると、最も適した特殊化が選択されます。たとえば、テンプレートと2つの部分的な特殊化を定義しましょう。
template<typename T, typename U, typename V>
struct S {
static void foo() {
std::cout << "General case\n";
}
};
template<typename U, typename V>
struct S<int, U, V> {
static void foo() {
std::cout << "T = int\n";
}
};
template<typename V>
struct S<int, double, V> {
static void foo() {
std::cout << "T = int, U = double\n";
}
};
今すぐ以下の呼び出し:
S<std::string, int, double>::foo();
S<int, float, std::string>::foo();
S<int, double, std::string>::foo();
印刷する
General case
T = int
T = int, U = double
関数テンプレートは完全に特殊化されているだけです:
template<typename T, typename U>
void foo(T t, U u) {
std::cout << "General case: " << t << " " << u << std::endl;
}
// OK.
template<>
void foo<int, int>(int a1, int a2) {
std::cout << "Two ints: " << a1 << " " << a2 << std::endl;
}
void invoke_foo() {
foo(1, 2.1); // Prints "General case: 1 2.1"
foo(1,2); // Prints "Two ints: 1 2"
}
// Compilation error: partial function specialization is not allowed.
template<typename U>
void foo<std::string, U>(std::string t, U u) {
std::cout << "General case: " << t << " " << u << std::endl;
}
デフォルトのテンプレートパラメータ値
関数引数の場合と同様に、テンプレートパラメータはデフォルト値を持つことができます。デフォルト値を持つすべてのテンプレートパラメータは、テンプレートパラメータリストの末尾に宣言する必要があります。基本的な考え方は、テンプレートのインスタンス化中にデフォルト値を持つテンプレートパラメータを省略できるということです。
デフォルトのテンプレートパラメータ値の使用例:
template <class T, size_t N = 10>
struct my_array {
T arr[N];
};
int main() {
/* Default parameter is ignored, N = 5 */
my_array<int, 5> a;
/* Print the length of a.arr: 5 */
std::cout << sizeof(a.arr) / sizeof(int) << std::endl;
/* Last parameter is omitted, N = 10 */
my_array<int> b;
/* Print the length of a.arr: 10 */
std::cout << sizeof(b.arr) / sizeof(int) << std::endl;
}
エイリアステンプレート
基本的な例:
template<typename T> using pointer = T*;
この定義は、 pointer<T>
をT*
エイリアスにしT*
。例えば:
pointer<int> p = new int; // equivalent to: int* p = new int;
別名テンプレートは特殊化できません。ただし、構造体のネストされた型を参照することによって、間接的にその機能を取得できます。
template<typename T>
struct nonconst_pointer_helper { typedef T* type; };
template<typename T>
struct nonconst_pointer_helper<T const> { typedef T* type; };
template<typename T> using nonconst_pointer = nonconst_pointer_helper<T>::type;
テンプレートテンプレートパラメータ
場合によっては、値を固定せずにテンプレートにテンプレート型を渡したい場合があります。これはテンプレートテンプレートパラメータが作成されるためのものです。非常に単純なテンプレートテンプレートのパラメータの例:
template <class T>
struct Tag1 { };
template <class T>
struct Tag2 { };
template <template <class> class Tag>
struct IntTag {
typedef Tag<int> type;
};
int main() {
IntTag<Tag1>::type t;
}
#include <vector>
#include <iostream>
template <class T, template <class...> class C, class U>
C<T> cast_all(const C<U> &c) {
C<T> result(c.begin(), c.end());
return result;
}
int main() {
std::vector<float> vf = {1.2, 2.6, 3.7};
auto vi = cast_all<int>(vf);
for(auto &&i: vi) {
std::cout << i << std::endl;
}
}
非型テンプレート引数をautoで宣言する
C ++ 17以前では、テンプレート型以外のパラメータを記述するときは、その型を最初に指定しなければなりませんでした。だから、共通のパターンは次のようなものになりました:
template <class T, T N>
struct integral_constant {
using type = T;
static constexpr T value = N;
};
using five = integral_constant<int, 5>;
しかし、複雑な式の場合、このようなものを使うにはdecltype(expr), expr
テンプレートをインスタンス化するときにdecltype(expr), expr
を書く必要があります。解決方法は、このイディオムを単純化し、単にauto
許可することです:
template <auto N>
struct integral_constant {
using type = decltype(N);
static constexpr type value = N;
};
using five = integral_constant<5>;
unique_ptrの空のカスタムデリター
素敵な動機の例では用のカスタム削除手段と、空のベースの最適化を結合しようとしてから来ることができるunique_ptr
。異なるC APIのDeleterは戻り値の型が異なりますが、関心事はありません。何らかの関数で動作するようにしたいだけです。
template <auto DeleteFn>
struct FunctionDeleter {
template <class T>
void operator()(T* ptr) const {
DeleteFn(ptr);
}
};
template <T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;
そして、戻り値の型に関わらず、型T
引数をテンプレートの型のないパラメータとして取ることができる関数ポインタを単純に使うことができ、そこからサイズのオーバーヘッドのunique_ptr
取り出すことができます:
unique_ptr_deleter<std::FILE, std::fclose> p;
非タイプのテンプレートパラメータ
テンプレートパラメータの型とは別に、次のいずれかの条件を満たす定数式の値を宣言することができます。
- 整数型または列挙型、
- オブジェクトへのポインタまたは関数へのポインタ、
- lvalue関数へのオブジェクト参照または左辺参照、
- メンバーへのポインタ、
-
std::nullptr_t
。
すべてのテンプレートパラメータと同様に、非型テンプレートパラメータは、明示的に指定するか、デフォルトにするか、またはテンプレート引数の引き落としを使用して暗黙的に導出できます。
非タイプのテンプレートパラメータの使用例:
#include <iostream>
template<typename T, std::size_t size>
std::size_t size_of(T (&anArray)[size]) // Pass array by reference. Requires.
{ // an exact size. We allow all sizes
return size; // by using a template "size".
}
int main()
{
char anArrayOfChar[15];
std::cout << "anArrayOfChar: " << size_of(anArrayOfChar) << "\n";
int anArrayOfData[] = {1,2,3,4,5,6,7,8,9};
std::cout << "anArrayOfData: " << size_of(anArrayOfData) << "\n";
}
タイプと非タイプの両方のテンプレートパラメータを明示的に指定する例:
#include <array>
int main ()
{
std::array<int, 5> foo; // int is a type parameter, 5 is non-type
}
非タイプのテンプレートパラメータは、テンプレートの再帰を実現する方法の1つであり、 メタプログラミングを可能にします。
可変的なテンプレートデータ構造
コンパイル時に定義される可変数と型のデータメンバーを持つクラスまたは構造体を定義すると便利なことがよくあります。標準的な例はstd::tuple
ですが、独自のカスタム構造を定義する必要があることもあります。次の例は、 std::tuple
ような継承ではなく、複合を使用して構造体を定義する例です。一般的な(空の)定義で始まります。これは、後の特殊化での巻き戻し終了のベースケースとしても機能します。
template<typename ... T>
struct DataStructure {};
これにより、空の構造体、 DataStructure<> data
を定義することはすでに可能ですが、まだそれほど有用ではありません。
次に、再帰的なケースの特殊化が行われます。
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
T first;
DataStructure<Rest ... > rest;
};
これで、 DataStructure<int, float, std::string> data(1, 2.1, "hello")
ような任意のデータ構造を作成するだけで十分です。
どうしたの?最初に、これは、少なくとも1つの可変的なテンプレートパラメータ(すなわち、上記のT
)が存在し、パックRest
特定の構成を気にしないという要件を有する専門化であることに留意されたい。 T
が存在することを知ることで、 first
データメンバーの定義が可能になります。残りのデータは、 DataStructure<Rest ... > rest
として再帰的にパッケージされます。コンストラクターは、 rest
メンバーへの再帰コンストラクター呼び出しを含む、これらのメンバーの両方を開始します。
これをよりよく理解するために、例を使って作業することができますDataStructure<int, float> data
という宣言があるとします。宣言は最初に特殊化と一致し、 int first
とDataStructure<float> rest
データメンバーを持つ構造体を生成します。 rest
定義は、この特殊化と再び一致し、独自のfloat first
とDataStructure<> rest
メンバーを作成します。最後に、この最後のrest
は、ベースケースの定義と一致し、空の構造を生成します。
これを次のように視覚化することができます:
DataStructure<int, float>
-> int first
-> DataStructure<float> rest
-> float first
-> DataStructure<> rest
-> (empty)
今はデータ構造を持っていますが、個々のデータ要素に簡単にアクセスすることはできません( DataStructure<int, float, std::string> data
の最後のメンバーにアクセスdata.rest.rest.first
、正確にはユーザーフレンドリーではありません)。だから我々は、追加get
(ベースケースの構造はするデータがないとしてのみ専門に必要なことにメソッドをget
):
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
...
template<size_t idx>
auto get()
{
return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
}
...
};
ご覧のように、 get
メンバ関数自体はテンプレート化されています(この時間は、必要なメンバのインデックス上にあります。したがって、 data.get<1>()
はstd::tuple
似たdata.get<1>()
ようなものになります)。実際の作業は、ヘルパクラスGetHelper
静的関数によって行われます。 DataStructure
のget
直接必要な機能を定義することができないのは、 idx
を特化する必要があるからです(ただし、すぐにわかるように)が、 idx
クラスを特化せずにテンプレートメンバー関数を特殊化することはできませんテンプレート。ここでC ++ 14スタイルのauto
使うと、戻り値の型がかなり複雑になるので、私たちの生活ははるかに簡単になります。
それでヘルパークラスに。今回は空のフォワード宣言と2つのスペシャライゼーションが必要になります。最初に宣言:
template<size_t idx, typename T>
struct GetHelper;
今、ベースケース( idx==0
とき)。この場合、 first
メンバを返すだけです。
template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
static T get(DataStructure<T, Rest...>& data)
{
return data.first;
}
};
再帰的なケースでは、 idx
を減らし、 rest
メンバーのGetHelper
を呼び出します。
template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
static auto get(DataStructure<T, Rest...>& data)
{
return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
}
};
例をdata.get<1>()
はDataStructure<int, float> data
があり、 data.get<1>()
が必要であるとします。これはGetHelper<1, DataStructure<int, float>>::get(data)
GetHelper<0, DataStructure<float>>::get(data.rest)
呼び出すGetHelper<1, DataStructure<int, float>>::get(data)
(2番目の特殊化)を呼び出します。 (今のidx
ような1番目の専門化によって) data.rest.first
。
そうです! main
関数での使用例をいくつか挙げてみましょう:
#include <iostream>
template<size_t idx, typename T>
struct GetHelper;
template<typename ... T>
struct DataStructure
{
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
T first;
DataStructure<Rest ... > rest;
template<size_t idx>
auto get()
{
return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
}
};
template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
static T get(DataStructure<T, Rest...>& data)
{
return data.first;
}
};
template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
static auto get(DataStructure<T, Rest...>& data)
{
return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
}
};
int main()
{
DataStructure<int, float, std::string> data(1, 2.1, "Hello");
std::cout << data.get<0>() << std::endl;
std::cout << data.get<1>() << std::endl;
std::cout << data.get<2>() << std::endl;
return 0;
}
明示的なインスタンス生成
明示的なインスタンス化の定義は、テンプレートを使用せずに、具体的なクラス、関数、または変数をテンプレートから作成および宣言します。明示的なインスタンス化は、他の翻訳単位から参照することができます。これは、有限の引数セットでのみインスタンス化される場合、ヘッダーファイルでのテンプレートの定義を避けるために使用できます。例えば:
// print_string.h
template <class T>
void print_string(const T* str);
// print_string.cpp
#include "print_string.h"
template void print_string(const char*);
template void print_string(const wchar_t*);
print_string<char>
とprint_string<wchar_t>
はprint_string.cpp
で明示的にインスタンス化されているため、 print_string
テンプレートがヘッダーに定義されていなくても、リンカーはそれらを見つけることができます。これらの明示的なインスタンス化宣言が存在しない場合、リンカエラーが発生する可能性があります。 ヘッダーファイルにテンプレートのみを実装できる理由を参照してください。
明示的なインスタンス化定義の前にextern
キーワードがある場合、代わりに明示的なインスタンス化宣言になります。指定された特殊化のための明示的なインスタンス化宣言の存在は、現在の変換単位内の指定された特殊化の暗黙的なインスタンス化を防ぎます。代わりに暗黙的なインスタンス化を引き起こすその専門化への参照は、同一または別のTU内の明示的なインスタンス化定義を参照することができます。
foo.h
#ifndef FOO_H
#define FOO_H
template <class T> void foo(T x) {
// complicated implementation
}
#endif
foo.cpp
#include "foo.h"
// explicit instantiation definitions for common cases
template void foo(int);
template void foo(double);
main.cpp
#include "foo.h"
// we already know foo.cpp has explicit instantiation definitions for these
extern template void foo(double);
int main() {
foo(42); // instantiates foo<int> here;
// wasteful since foo.cpp provides an explicit instantiation already!
foo(3.14); // does not instantiate foo<double> here;
// uses instantiation of foo<double> in foo.cpp instead
}