Namespaces
Variants

Function template

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Class template
Function template
Miscellaneous

関数テンプレートは関数のファミリーを定義します。

目次

構文

template < parameter-list > function-declaration (1)
template < parameter-list > requires constraint function-declaration (2) (C++20以降)
function-declaration-with-placeholders (3) (C++20以降)
export template < parameter-list > function-declaration (4) (C++11で削除)

説明

parameter-list - 空でないカンマ区切りの テンプレートパラメータ のリスト。各パラメータは 定数パラメータ 型パラメータ テンプレートパラメータ 、またはこれらのいずれかの パラメータパック (C++11以降) のいずれか。 任意のテンプレートと同様に、パラメータは 制約 を付けることが可能 (C++20以降)
function-declaration - 関数宣言 。宣言された関数名はテンプレート名となる。
constraint - この関数テンプレートが受け入れるテンプレートパラメータを制限する 制約式
function-declaration-
with-placeholders
- 少なくとも1つのパラメータの型がプレースホルダー auto または Concept auto を使用する 関数宣言 :テンプレートパラメータリストには、各プレースホルダーに対して1つの生成されたパラメータが含まれる(後述の簡略関数テンプレートを参照)

export はオプションの修飾子であり、テンプレートをエクスポート済みとして宣言していました(クラステンプレートと共に使用する場合、そのすべてのメンバーもエクスポート済みとして宣言されます)。エクスポートされたテンプレートをインスタンス化するファイルは、その定義を含める必要はありませんでした:宣言だけで十分でした。 export の実装は稀であり、詳細について互いに合意がありませんでした。

(C++11まで)

省略関数テンプレート

プレースホルダ型( auto または Concept auto )が関数宣言または関数テンプレート宣言のパラメータリストに現れる場合、その宣言は関数テンプレートを宣言し、各プレースホルダに対して1つの発明されたテンプレートパラメータがテンプレートパラメータリストに追加されます:

void f1(auto); // same as template<class T> void f1(T)
void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept
void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept
void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&);
template<class T, C U>
void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z);

省略関数テンプレートは、すべての関数テンプレートと同様に特殊化できます。

template<>
void f4<int>(const int*, const double&); // specialization of f4<int, const double>


(C++20以降)

関数テンプレートシグネチャ

すべての関数テンプレートにはシグネチャがあります。

テンプレートヘッドのシグネチャは、 テンプレートパラメータリスト から、テンプレートパラメータ名と デフォルト引数 、およびrequires節(もしあれば) (C++20以降) を除いたものである。

関数テンプレートのシグネチャは、名前、パラメータ型リスト、戻り値型 、末尾のrequires節(もしあれば) (C++20以降) 、および template-head のシグネチャを含む。以下の場合を除き、そのシグネチャは囲んでいる名前空間も含む。

関数テンプレートがクラスメンバである場合、そのシグネチャには、外側の名前空間ではなく、その関数がメンバであるクラスが含まれます。そのシグネチャにはまた、 末尾のrequires節(もしあれば) (C++20以降) 、ref修飾子(もしあれば)、および (C++11以降) cv 修飾子(もしあれば)も含まれます。

関数テンプレートが、外側のテンプレートパラメータを含む制約を持つ friend である場合、そのシグネチャは外側の名前空間ではなく外側のクラスを含みます。

(C++20以降)

関数テンプレートのインスタンス化

関数テンプレート自体は型や関数ではありません。テンプレート定義のみを含むソースファイルからはコードは生成されません。コードを生成するためには、テンプレートをインスタンス化する必要があります:コンパイラが実際の関数(またはクラステンプレートからのクラス)を生成できるように、テンプレート引数を決定する必要があります。

明示的インスタンス化

template 戻り値型 名前 < 引数リスト > ( パラメータリスト ) ; (1)
template 戻り値型 名前 ( パラメータリスト ) ; (2)
extern template 戻り値型 名前 < 引数リスト > ( パラメータリスト ) ; (3) (C++11以降)
extern template 戻り値型 名前 ( パラメータリスト ) ; (4) (C++11以降)
1) 明示的インスタンス化定義( template argument deduction なし、すべての非デフォルトテンプレートパラメータが明示的に指定されている場合)
2) すべてのパラメータに対するテンプレート引数推論を伴う明示的インスタンス化定義
3) 明示的インスタンス化宣言(すべての非デフォルトテンプレートパラメータが明示的に指定されている場合、テンプレート引数推論を行わない)
4) すべてのパラメータに対するテンプレート引数推論を伴う明示的インスタンス化宣言

明示的なインスタンス化定義は、それが参照する関数またはメンバ関数のインスタンス化を強制します。これはテンプレート定義の後であればプログラム内のどこにでも現れることができ、与えられた引数リストに対しては、プログラム内に一度だけ現れることが許可され、診断は要求されません。

明示的なインスタンス化宣言(extern template)は暗黙的なインスタンス化を防止します:本来であれば暗黙的なインスタンス化を引き起こすコードは、プログラム内の別の場所で提供される明示的なインスタンス化定義を使用しなければなりません。

(since C++11)

関数テンプレートの特殊化またはメンバ関数テンプレートの特殊化の明示的インスタンス化では、関数パラメータから 推論 可能な場合、末尾のテンプレート引数を省略できます:

template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
template void f<double>(double); // f<double>(double) をインスタンス化
template void f<>(char);         // f<char>(char) をインスタンス化、テンプレート引数は推論
template void f(int);            // f<int>(int) をインスタンス化、テンプレート引数は推論

関数テンプレートまたはクラステンプレートのメンバー関数の明示的なインスタンス化は、 inline または constexpr を使用できません。明示的なインスタンス化の宣言が暗黙的に宣言された特殊メンバー関数を指定する場合、プログラムは不適格となります。

コンストラクタの 明示的インスタンス化 はテンプレートパラメータリストを使用できません(構文 (1) )。これはまた、推論可能であるため(構文 (2) )決して必要とされません。

プロスペクティブデストラクタ の明示的インスタンス化は、クラスの選択されたデストラクタを指定しなければならない。

(C++20 以降)

明示的なインスタンス化宣言は、 inline 関数、 auto 宣言、参照、およびクラステンプレートの特殊化の暗黙的なインスタンス化を抑制しません。(したがって、明示的なインスタンス化宣言の対象であるinline関数がODR使用された場合、インライン化のために暗黙的にインスタンス化されますが、この翻訳単位ではout-of-lineコピーは生成されません)

デフォルト引数を持つ関数テンプレートの明示的なインスタンス化定義は、 デフォルト引数 の使用ではなく、それらの初期化を試みるものではありません:

char* p = 0;
template<class T>
T g(T x = &p) { return x; }
template int g<int>(int); // &pがintではないにも関わらず正常

暗黙のインスタンス化

コードが関数を参照する際に 関数定義の存在が必須となる文脈 、または定義の存在がプログラムの意味論に影響を与える場合 (C++11以降) 、かつその特定の関数が明示的にインスタンス化されていないとき、暗黙的なインスタンス化が発生します。テンプレート引数のリストは、文脈から 推論 可能な場合には指定する必要がありません。

#include <iostream>
template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
int main()
{
    f<double>(1); // f<double>(double) をインスタンス化して呼び出し
    f<>('a');     // f<char>(char) をインスタンス化して呼び出し
    f(7);         // f<int>(int) をインスタンス化して呼び出し
    void (*pf)(std::string) = f; // f<string>(string) をインスタンス化
    pf("∇");                     // f<string>(string) を呼び出し
}

関数の定義の存在は、その関数が式によって 定数評価に必要 とされる場合、たとえ式の定数評価が要求されない場合や、定数式評価がその定義を使用しない場合でも、プログラムのセマンティクスに影響を与えると見なされる。

template<typename T>
constexpr int f() { return T::value; }
template<bool B, typename T>
void g(decltype(B ? f<T>() : 0));
template<bool B, typename T>
void g(...);
template<bool B, typename T>
void h(decltype(int{B ? f<T>() : 0}));
template<bool B, typename T>
void h(...);
void x()
{
    g<false, int>(0); // OK: B ? f<T>() : 0 is not potentially constant evaluated
    h<false, int>(0); // error: instantiates f<int> even though B evaluates to false
                      // and list-initialization of int from int cannot be narrowing
}
(C++11以降)

注: <> を完全に省略すると、 overload resolution がテンプレートと非テンプレートの両方のオーバーロードを検査できるようになります。

テンプレート引数推論

関数テンプレートをインスタンス化するには、すべてのテンプレート引数が既知である必要がありますが、すべてのテンプレート引数を指定する必要はありません。可能な場合、コンパイラは関数引数から不足しているテンプレート引数を推論します。これは、関数呼び出しが試行されたときや、関数テンプレートのアドレスが取得されたときに発生します。

template<typename To, typename From>
To convert(From f);
void g(double d) 
{
    int i = convert<int>(d);    // convert<int,double>(double) を呼び出す
    char c = convert<char>(d);  // convert<char,double>(double) を呼び出す
    int(*ptr)(float) = convert; // convert<int, float>(float) をインスタンス化する
}

この仕組みにより、テンプレート演算子を使用することが可能になります。演算子に対してテンプレート引数を指定する構文が存在しないため、関数呼び出し式として書き直す以外に方法がないからです。

#include <iostream>
int main() 
{
    std::cout << "Hello, world" << std::endl;
    // operator<< は ADL を通じて std::operator<< として検索され、
    // 両方とも operator<<<char, std::char_traits<char>> と推論される
    // std::endl は &std::endl<char, std::char_traits<char>> と推論される
}

テンプレート引数の推論は、関数テンプレートの name lookup argument-dependent lookup を含む場合がある)の後、 overload resolution の前に行われます。

詳細については テンプレート引数推論 を参照してください。

明示的テンプレート引数

関数テンプレートのテンプレート引数は、以下から取得できます

  • template argument deduction
  • default template arguments
  • specified explicitly, which can be done in the following contexts:
  • 関数呼び出し式内で
  • 関数のアドレスが取得された場合
  • 関数への参照が初期化された場合
  • メンバ関数へのポインタが形成された場合
  • 明示的特殊化内で
  • 明示的インスタンス化内で
  • フレンド宣言内で

テンプレート引数を明示的に指定する方法はありません。 オーバーロードされた演算子 変換関数 、およびコンストラクタに対しては、これらが関数名を使用せずに呼び出されるためです。

指定されたテンプレート引数は、種類においてテンプレートパラメータと一致しなければなりません(すなわち、型に対して型、定数に対して定数、テンプレートに対してテンプレート)。パラメータよりも多くの引数を指定することはできません (ただし、1つのパラメータがパラメータパックである場合を除く。この場合、非パックパラメータごとに引数が必要です) (C++11以降)

指定された定数引数は、対応する定数テンプレートパラメータの型と一致するか、 それらに変換可能 でなければなりません。

テンプレート引数推論に参加しない関数パラメータ(例えば対応するテンプレート引数が明示的に指定されている場合)は、対応する関数パラメータの型への暗黙変換の対象となります(通常の オーバーロード解決 と同様に)。

明示的に指定されたテンプレートパラメータパックは、追加の引数がある場合にテンプレート引数推論によって拡張されることがあります:

template<class... Types>
void f(Types... values);
void g()
{
    f<int*, float*>(0, 0, 0); // Types = {int*, float*, int}
}
(C++11以降)

テンプレート引数の置換

すべてのテンプレート引数が指定され、推論されるか、デフォルトのテンプレート引数から取得された後、関数パラメータリスト内のテンプレートパラメータのすべての使用は、対応するテンプレート引数で置き換えられます。

関数テンプレートの置換失敗(つまり、推定されたまたは提供されたテンプレート引数でテンプレートパラメータを置換する失敗)は、その関数テンプレートを オーバーロードセット から除去します。これにより、テンプレートメタプログラミングを使用してオーバーロードセットを操作する多数の方法が可能になります:詳細は SFINAE を参照してください。

置換後、配列型および関数型のすべての関数パラメータはポインタに調整され、すべてのトップレベルのcv修飾子が関数パラメータから削除されます(通常の 関数宣言 と同様に)。

トップレベルのcv修飾子の除去は、関数内に現れるパラメータの型に影響しません:

template<class T>
void f(T t);
template<class X>
void g(const X x);
template<class Z>
void h(Z z, Z* zp);
// 同じ型を持つ2つの異なる関数だが、
// 関数内ではtが異なるCV修飾を持つ
f<int>(1);       // 関数型はvoid(int)、tはint
f<const int>(1); // 関数型はvoid(int)、tはconst int
// 同じ型と同じxを持つ2つの異なる関数
// (これらの2つの関数へのポインタは等しくなく、
//  関数ローカルのstatic変数は異なるアドレスを持つ)
g<int>(1);       // 関数型はvoid(int)、xはconst int
g<const int>(1); // 関数型はvoid(int)、xはconst int
// トップレベルのCV修飾子のみが除去される:
h<const int>(1, NULL); // 関数型はvoid(int, const int*) 
                       // zはconst int、zpはconst int*

関数テンプレートのオーバーロード

関数テンプレートと非テンプレート関数はオーバーロード可能です。

非テンプレート関数は、同じ型を持つテンプレート特殊化とは常に区別されます。異なる関数テンプレートの特殊化は、同じ型を持つ場合でも常に互いに区別されます。同じ戻り値の型と同じパラメータリストを持つ2つの関数テンプレートは区別可能であり、明示的なテンプレート引数リストによって識別できます。

型または定数テンプレートパラメーターを使用する式が関数パラメーターリストまたは戻り値の型に現れる場合、その式はオーバーロードの目的において関数テンプレートシグネチャの一部として残ります:

template<int I, int J>
A<I+J> f(A<I>, A<J>); // オーバーロード #1
template<int K, int L>
A<K+L> f(A<K>, A<L>); // #1 と同じ
template<int I, int J>
A<I-J> f(A<I>, A<J>); // オーバーロード #2

テンプレートパラメータを含む2つの式は、それらの式を含む2つの関数定義が ODR の下で同じになる場合、つまり、2つの式が同じトークン列を含み、その名前が名前探索によって同じエンティティに解決される場合(ただし、テンプレートパラメータの名前は異なっていてもよい)、 等価 と呼ばれる。 2つの ラムダ式 は決して等価ではない。 (C++20以降)

template<int I, int J>
void f(A<I+J>); // テンプレートオーバーロード #1
template<int K, int L>
void f(A<K+L>); // #1と等価

2つの dependent expressions が等価かどうかを判定する際には、関与するdependent namesのみが考慮され、名前探索の結果は考慮されません。同じテンプレートの複数の宣言が名前探索の結果で異なる場合、最初のそのような宣言が使用されます:

template<class T>
decltype(g(T())) h(); // decltype(g(T())) は依存型
int g(int);
template<class T>
decltype(g(T())) h()
{                  // h() の再宣言では以前のルックアップが使用される
    return g(T()); // ここのルックアップでは g(int) が見つかるにもかかわらず
}
int i = h<int>(); // テンプレート引数置換が失敗; g(int)
                  // は h() の最初の宣言時にはスコープ内になかった

2つの関数テンプレートは、以下の場合に 等価 と見なされます

  • それらは同じスコープで宣言されている
  • それらは同じ名前を持っている
  • それらは 等価な テンプレートパラメータリストを持っている。つまり、リストの長さが同じであり、対応する各パラメータペアについて以下のすべてが真である:
  • 2つのパラメータが同じ種類である(両方とも型、両方とも定数、または両方ともテンプレート)
  • 両方がパラメータパックであるか、あるいはどちらもパラメータパックでないかのいずれか
(C++11以降)
  • 定数の場合、それらの型は等価であり、
  • テンプレートの場合、それらのテンプレートパラメータは等価です。
  • 一方がconcept-nameで宣言されている場合、両方がconcept-nameで宣言されており、かつconcept-nameが等価である。
(C++20以降)
  • 戻り値の型とパラメータリストにおけるテンプレートパラメータを含む式は 等価 である
  • テンプレートパラメータリストに続くrequires節(存在する場合)内の式が等価である
  • 関数宣言子に続くrequires節(存在する場合)内の式が等価である
(C++20以降)

テンプレートパラメータを含む2つの 潜在的に評価される (C++20以降) 式は、それらが 等価 ではないが、任意のテンプレート引数のセットに対して、2つの式の評価が同じ値になる場合、 機能的に等価 と呼ばれます。

2つの関数テンプレートは、それらが 等価 である場合を除き、戻り値の型とパラメータリスト内のテンプレートパラメータを含む1つ以上の式が 機能的に等価 である場合、 機能的に等価 と見なされます。

さらに、2つの関数テンプレートが、その制約が異なる方法で指定されているが、同じテンプレート引数リストのセットを受け入れ、満たす場合、それらは 機能的に等価 であるが 等価 ではない。

(C++20以降)

プログラムが、 機能的に等価 であるが 等価 ではない関数テンプレートの宣言を含む場合、そのプログラムは不適格である。診断は要求されない。

// 等価
template<int I>
void f(A<I>, A<I+10>); // オーバーロード #1
template<int I>
void f(A<I>, A<I+10>); // オーバーロード #1 の再宣言
// 等価ではない
template<int I>
void f(A<I>, A<I+10>); // オーバーロード #1
template<int I>
void f(A<I>, A<I+11>); // オーバーロード #2
// 機能的に等価だが等価ではない
// このプログラムは不適格であり、診断は要求されない
template<int I>
void f(A<I>, A<I+10>);      // オーバーロード #1
template<int I>
void f(A<I>, A<I+1+2+3+4>); // 機能的に等価

同じ関数テンプレートの特殊化が複数のオーバーロードされた関数テンプレートにマッチする場合(これはしばしば template argument deduction から生じます)、最適なマッチを選択するために partial ordering of overloaded function templates が実行されます。

具体的には、部分順序付けは以下の状況で行われます:

1) オーバーロード解決 関数テンプレート特殊化の呼び出しにおける:
template<class X>
void f(X a);
template<class X>
void f(X* a);
int* p;
f(p);
2) 関数テンプレートの特殊化の アドレスが取得される場合 :
template<class X>
void f(X a);
template<class X>
void f(X* a);
void (*p)(int*) = &f;
3) プレースメント operator delete が関数テンプレートの特殊化であり、プレースメント operator new にマッチするように選択された場合:
4) 以下の場合: friend関数宣言 明示的インスタンス化 または 明示的特殊化 が関数テンプレートの特殊化を参照する場合:
template<class X>
void f(X a);  // 最初のテンプレートf
template<class X>
void f(X* a); // 2番目のテンプレートf
template<>
void f<>(int *a) {} // 明示的特殊化
// テンプレート引数推論により2つの候補が生成される:
// f<int*>(int*) と f<int>(int*)
// 部分順序付けにより f<int>(int*) がより特殊化されたものとして選択される

非公式には「AはBよりも特殊化されている」とは、「AがBよりも少ない型を受け入れる」ことを意味します。

形式的には、任意の2つの関数テンプレートのどちらがより特殊化されているかを決定するために、部分順序付けプロセスはまず2つのテンプレートのうちの1つを以下のように変換します:

  • 各型、定数、およびテンプレートパラメータに対して、 パラメータパックを含む、 (C++11以降) 固有の仮想型、値、またはテンプレートが生成され、テンプレートの関数型に代入される
  • 比較対象の2つの関数テンプレートのうち一方のみがメンバ関数であり、かつその関数テンプレートが何らかのクラス A の非静的メンバである場合、そのパラメータリストに新たな第一パラメータが挿入される。 cv を関数テンプレートのcv修飾子 とし、 ref を関数テンプレートの参照修飾子 (C++11以降) とした場合、新しいパラメータの型は cv A& となる。ただし ref && の場合、または ref が存在せず、かつ他方のテンプレートの第一パラメータが右辺値参照型である場合は、型は cv A&& となる (C++11以降) 。これは、メンバ関数と非メンバ関数の両方として検索される演算子の順序付けを補助する:
struct A {};
template<class T>
struct B
{
    template<class R>
    int operator*(R&); // #1
};
template<class T, class R>
int operator*(T&, R&); // #2
int main()
{
    A a;
    B<A> b;
    b * a; // int B<A>::operator*(R&) に対するテンプレート引数推論は R=A を与える
           //                             int operator*(T&, R&) に対しては T=B<A>, R=A
    // 部分順序付けの目的で、メンバーテンプレート B<A>::operator* は
    // template<class R> int operator*(B<A>&, R&); に変換される
    // 以下の間の部分順序付け:
    //     int operator*(   T&, R&)  T=B<A>, R=A
    // と int operator*(B<A>&, R&)  R=A 
    // により、より特殊化された int operator*(B<A>&, A&) が選択される
}

2つのテンプレートのうち1つが上記のように変換された後、 template argument deduction が実行され、変換されたテンプレートを引数テンプレートとして、もう一方のテンプレートの元のテンプレート型をパラメータテンプレートとして使用します。その後、2番目のテンプレート(変換後)を引数として、元の形式の最初のテンプレートをパラメータとして使用して、このプロセスが繰り返されます。

順序を決定するために使用される型は、コンテキストによって異なります:

  • 関数呼び出しの文脈では、型とは関数呼び出しに実引数を持つ関数引数型のことです(デフォルト関数引数、 パラメータパック、 (C++11以降) および省略記号パラメータは考慮されません -- 以下の例を参照)
  • ユーザー定義変換関数の呼び出しの文脈では、変換関数テンプレートの戻り値の型が使用されます
  • その他の文脈では、関数テンプレートの型が使用されます

上記のリストからの各型は、パラメーターテンプレートから推論されます。推論が開始される前に、パラメーターテンプレートの各パラメーター P および引数テンプレートの対応する引数 A は以下のように調整されます:

  • 両方の P A が元々参照型である場合、どちらがよりcv修飾されているかを判定する(その他の場合、部分順序付けの目的ではcv修飾は無視される)
  • P が参照型の場合、参照先の型で置き換えられる
  • A が参照型の場合、参照先の型で置き換えられる
  • P がcv修飾されている場合、 P は自身のcv非修飾版で置き換えられる
  • A がcv修飾されている場合、 A は自身のcv非修飾版で置き換えられる

これらの調整の後、 P から A の導出は、 型からのテンプレート引数導出 に従って行われます。

P が関数パラメータパックの場合、引数テンプレートの各残りのパラメータ型の型 A は、関数パラメータパックの宣言子IDの型 P と比較されます。各比較は、関数パラメータパックによって展開されるテンプレートパラメータパック内の後続の位置に対してテンプレート引数を推論します。

A が関数パラメータパックから変換された場合、それはパラメータテンプレートの各残りのパラメータ型と比較されます。

(C++11以降)

変換されたテンプレート-1の引数 A がテンプレート-2の対応するパラメータ P の推論に使用できるが、逆は成立しない場合、この A P よりも、この P/A ペアによって推論される型に関してより特殊化されている。

両方向で推論が成功し、元の P A が参照型であった場合、追加のテストが行われます:

  • A が左辺値参照であり、 P が右辺値参照である場合、 A P よりも特殊化されていると見なされます
  • A P よりもCV修飾されている場合、 A P よりも特殊化されていると見なされます

その他のすべての場合において、この P/A ペアによって推定される型に関して、どちらのテンプレートも他方よりも特殊化されているとは見なされません。

両方向のすべての P A を検討した後、検討された各型について、

  • template-1 はすべての型について template-2 と少なくとも同等に特殊化されている
  • template-1 は一部の型について template-2 よりもより特殊化されている
  • template-2 はどの型についても template-1 よりもより特殊化されていない、あるいはどの型についても少なくとも同等に特殊化されていない

その後、template-1はtemplate-2よりも特殊化されています。上記の条件がテンプレートの順序を入れ替えた後も真である場合、template-2はtemplate-1よりも特殊化されています。それ以外の場合、どちらのテンプレートも他方よりも特殊化されているとは言えません。

同点の場合、一方の関数テンプレートが末尾のパラメータパックを持ち、他方が持たない場合、パラメータが省略された方が空のパラメータパックを持つものよりもより特殊化されていると見なされます。

(C++11以降)

オーバーロードされたテンプレートの全てのペアを考慮した後、他と比べて明確により特殊化されているものが1つ存在する場合、そのテンプレートの特殊化が選択されます。それ以外の場合、コンパイルは失敗します。

以下の例では、架空の引数をU1、U2と呼びます:

template<class T>
void f(T);        // テンプレート #1
template<class T>
void f(T*);       // テンプレート #2
template<class T>
void f(const T*); // テンプレート #3
void m()
{
    const int* p;
    f(p); // オーバーロード解決の選択: #1: void f(T ) [T = const int *]
          //                            #2: void f(T*) [T = const int]
          //                            #3: void f(const T *) [T = int]
    // 部分的順序付け:
    // #1 から変換された #2: void(T) から void(U1*): P=T A=U1*: 推定成功: T=U1*
    // #2 から変換された #1: void(T*) から void(U1): P=T* A=U1: 推定失敗
    // #2 は #1 よりも T に関してより特殊化されている
    // #1 から変換された #3: void(T) から void(const U1*): P=T, A=const U1*: 成功
    // #3 から変換された #1: void(const T*) から void(U1): P=const T*, A=U1: 失敗
    // #3 は #1 よりも T に関してより特殊化されている
    // #2 から変換された #3: void(T*) から void(const U1*): P=T* A=const U1*: 成功
    // #3 から変換された #2: void(const T*) から void(U1*): P=const T* A=U1*: 失敗
    // #3 は #2 よりも T に関してより特殊化されている
    // 結果: #3 が選択される
    // 言い換えると、f(const T*) は f(T) や f(T*) よりもより特殊化されている
}
template<class T>
void f(T, T*);   // #1
template<class T>
void f(T, int*); // #2
void m(int* p)
{
    f(0, p); // #1の推論: void f(T, T*) [T = int]
             // #2の推論: void f(T, int*) [T = int]
    // 部分順序付け:
    // #1から#2: void(T,T*)からvoid(U1,int*): P1=T, A1=U1: T=U1
    //                                  P2=T*, A2=int*: T=int: 失敗
    // #2から#1: void(T,int*)からvoid(U1,U2*): P1=T A1=U1: T=U1
    //                                  P2=int* A2=U2*: 失敗
    // Tに関してどちらもより特殊化されていないため、呼び出しは曖昧
}
template<class T>
void g(T);  // テンプレート #1
template<class T>
void g(T&); // テンプレート #2
void m()
{
    float x;
    g(x); // #1からの推論: void g(T ) [T = float]
          // #2からの推論: void g(T&) [T = float]
    // 部分順序付け:
    // #2から見た#1: void(T) from void(U1&): P=T, A=U1 (調整後), ok
    // #1から見た#2: void(T&) from void(U1): P=T (調整後), A=U1: ok
    // Tに関してどちらもより特殊化されていないため、呼び出しは曖昧
}
template<class T>
struct A { A(); };
template<class T>
void h(const T&); // #1
template<class T>
void h(A<T>&);    // #2
void m()
{
    A<int> z;
    h(z); // #1からの推論: void h(const T &) [T = A<int>]
          // #2からの推論: void h(A<T> &) [T = int]
    // 部分順序付け:
    // #1から#2: void(const T&) から void(A<U1>&): P=T A=A<U1>: ok T=A<U1>
    // #2から#1: void(A<T>&) から void(const U1&): P=A<T> A=const U1: 失敗
    // #2は#1よりもTに関してより特殊化されている
    const A<int> z2;
    h(z2); // #1からの推論: void h(const T&) [T = A<int>]
           // #2からの推論: void h(A<T>&) [T = int]、ただし置換は失敗
    // 選択可能なオーバーロードは1つのみ、部分順序付けは試行されず、#1が呼び出される
}

呼び出しコンテキストは明示的な呼び出し引数があるパラメータのみを考慮するため、 関数パラメータパック、 (C++11以降) エリプシスパラメータ、および明示的な呼び出し引数がないデフォルト引数付きパラメータは無視されます:

template<class T>
void f(T);         // #1
template<class T>
void f(T*, int = 1); // #2
void m(int* ip)
{
    int* ip;
    f(ip); // #2を呼び出す(T*はTよりも特殊化されているため)
}
template<class T>
void g(T);       // #1
template<class T>
void g(T*, ...); // #2
void m(int* ip)
{
    g(ip); // #2を呼び出す(T*はTよりも特殊化されているため)
}
template<class T, class U>
struct A {};
template<class T, class U>
void f(U, A<U, T>* p = 0); // #1
template<class U>
void f(U, A<U, U>* p = 0); // #2
void h()
{
    f<int>(42, (A<int, int>*)0); // #2を呼び出す
    f<int>(42);                  // エラー: あいまい
}
template<class T>
void g(T, T = T()); // #1
template<class T, class... U>
void g(T, U...);    // #2
void h()
{
    g(42); // エラー: 曖昧
}
template<class T, class... U>
void f(T, U...); // #1
template<class T>
void f(T);       // #2
void h(int i)
{
    f(&i); // パラメータパックとパラメータなしの間のタイブレーカーにより #2 を呼び出す
           // (注: DR692 と DR1395 の間では曖昧でした)
}
template<class T, class... U>
void g(T*, U...); // #1
template<class T>
void g(T);        // #2
void h(int i)
{
    g(&i); // OK: #1を呼び出す(T*はTよりも特殊化されている)
}
template<class... T>
int f(T*...);    // #1
template<class T>
int f(const T&); // #2
f((int*)0); // OK: #2を選択; 可変長テンプレートよりも非可変長テンプレートの方がより特殊化されている
            // (DR1395以前は、両方向で推論が失敗したため曖昧であった)
template<class... Args>
void f(Args... args);        // #1
template<class T1, class... Args>
void f(T1 a1, Args... args); // #2
template<class T1, class T2>
void f(T1 a1, T2 a2);        // #3
f();        // #1を呼び出す
f(1, 2, 3); // #2を呼び出す
f(1, 2);    // #3を呼び出す; 可変長テンプレートではない#3は、
            // 可変長テンプレートの#1と#2よりも特殊化されている

部分順序付けプロセスのテンプレート引数推論中に、引数が部分順序付けのために考慮されるいずれの型でも使用されない場合、テンプレートパラメータは引数と一致する必要はありません

template<class T>
T f(int); // #1
template<class T, class U>
T f(U);   // #2
void g()
{
    f<int>(1); // #1の特殊化は明示的: T f(int) [T = int]
               // #2の特殊化は推論される: T f(U) [T = int, U = int]
    // 部分順序付け(引数の型のみを考慮):
    // #1から#2: T(int)からU1(U2): 失敗
    // #2から#1: T(U)からU1(int): 成功: U=int, T未使用
    // #1を呼び出す
}

テンプレートパラメータパックを含む関数テンプレートの部分順序付けは、それらのテンプレートパラメータパックに対して推定される引数の数に依存しない。

template<class...>
struct Tuple {};
template<class... Types>
void g(Tuple<Types...>);      // #1
template<class T1, class... Types>
void g(Tuple<T1, Types...>);  // #2
template<class T1, class... Types>
void g(Tuple<T1, Types&...>); // #3
g(Tuple<>());            // calls #1
g(Tuple<int, float>());  // calls #2
g(Tuple<int, float&>()); // calls #3
g(Tuple<int>());         // calls #3
(C++11以降)

関数テンプレートの呼び出しをコンパイルする際、コンパイラは非テンプレートオーバーロード、テンプレートオーバーロード、およびテンプレートオーバーロードの特殊化の中から選択する必要があります。

template<class T>
void f(T);      // #1: テンプレートのオーバーロード
template<class T>
void f(T*);     // #2: テンプレートのオーバーロード
void f(double); // #3: 非テンプレートのオーバーロード
template<>
void f(int);    // #4: #1の特殊化
f('a');        // #1を呼び出す
f(new int(1)); // #2を呼び出す
f(1.0);        // #3を呼び出す
f(1);          // #4を呼び出す

関数オーバーロードと関数特殊化

オーバーロード解決には、非テンプレートおよびプライマリテンプレートのオーバーロードのみが参加することに注意してください。特殊化はオーバーロードではなく、考慮されません。オーバーロード解決が最適に一致するプライマリ関数テンプレートを選択した後でのみ、その特殊化がより適切な一致であるかどうかが調査されます。

template<class T>
void f(T);    // #1: すべての型に対するオーバーロード
template<>
void f(int*); // #2: intポインタに対する#1の特殊化
template<class T>
void f(T*);   // #3: すべてのポインタ型に対するオーバーロード
f(new int(1)); // #3を呼び出す(#1の特殊化が完全一致であっても)

翻訳単位のヘッダーファイルを順序付ける際には、この規則を覚えておくことが重要です。関数オーバーロードと関数特殊化の相互作用に関するさらなる例については、以下を展開してください:

まず、引数依存の名前探索が使用されないいくつかのシナリオを考えてみましょう。そのために、呼び出し ( f ) ( t ) を使用します。 ADL で説明されているように、関数名を括弧で囲むことは引数依存の名前探索を抑制します。

  • point-of-reference (POR) の前に宣言された f ( ) の複数のオーバーロードが g ( ) 内に存在します。
#include <iostream>
struct A {};
template<class T>
void f(T)  { std::cout << "#1\n"; } // f()のPOF前のオーバーロード #1
template<class T>
void f(T*) { std::cout << "#2\n"; } // f()のPOF前のオーバーロード #2
template<class T>
void g(T* t) 
{
    (f)(t); // f()のPOF
}
int main()
{
    A* p = nullptr;
    g(p); // g()とf()のPOF
}
// #1と#2の両方が候補リストに追加される;
// #2はより適切なマッチであるため選択される。

出力:

#2


  • PORの後に、より適切に一致するテンプレートのオーバーロードが宣言されています。
#include <iostream>
struct A {};
template<class T>
void f(T)  { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// Only #1 is added to the candidate list; #2 is defined after POR;
// therefore, it is not considered for overloading even if it is a better match.

出力:

#1


  • PORの後に、より適切に一致する明示的なテンプレート特殊化が宣言されています。
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list; #3 is a better match defined after POR. The
// candidate list consists of #1 which is eventually selected. After that, the explicit 
// specialization #3 of #1 declared after POI is selected because it is a better match. 
// This behavior is governed by 14.7.3/6 [temp.expl.spec] and has nothing to do with ADL.

出力:

#3


  • PORの後に、より一致するテンプレートのオーバーロードが宣言されています。最も一致する明示的なテンプレート特殊化は、より一致するオーバーロードの後に宣言されています。
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is the only member of the candidate list and it is eventually selected. 
// After that, the explicit specialization #3 is skipped because it actually 
// specializes #2 declared after POR.

出力:

#1


ここで、引数依存ルックアップを使用するケースについて考えてみましょう(つまり、より一般的な呼び出し形式 f ( t ) を使用します)。

  • PORの後に、より適切に一致するテンプレートのオーバーロードが宣言されています。
#include <iostream>
struct A {};
template<class T>
void f(T)  { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected being the better match.

出力:

#2


  • PORの後に、より一致するテンプレートのオーバーロードが宣言されています。最も一致する明示的なテンプレート特殊化は、より一致するオーバーロードの前に宣言されています。
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared before #2, it is an explicit specialization of #1.
// Hence the final selection is #2.

出力:

#2


  • PORの後に、より適切にマッチするテンプレートのオーバーロードが宣言されています。最も適切にマッチする明示的なテンプレート特殊化が最後に宣言されています。
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared after #2, it is an explicit specialization of #2;
// therefore, selected as the function to call.

出力:

#3


引数が何らかのC++基本型である場合、ADL関連の名前空間は存在しません。したがって、これらのシナリオは上記の非ADLの例と同一です。

オーバーロード解決の詳細な規則については、 overload resolution を参照してください。

関数テンプレートの特殊化

キーワード

template , extern (C++11以降)

不具合報告

以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。

DR 適用バージョン 公開時の動作 修正後の動作
CWG 214 C++98 部分順序付けの正確な手順が規定されていなかった 仕様が追加された
CWG 532 C++98 非静的メンバ関数テンプレートと非メンバ関数テンプレート間の
順序が規定されていなかった
仕様が追加された
CWG 581 C++98 コンストラクタテンプレートの明示的特殊化または
インスタンス化におけるテンプレート引数リストが許可されていた
禁止された
CWG 1321 C++98 最初の宣言と再宣言における同じ依存名が等価かどうかが不明確だった それらは等価であり、
意味は最初の宣言と
同じである
CWG 1395 C++11 Aがパックからのものであり、空のパックのタイブレーカーが
存在しない場合に推論が失敗していた
推論が許可され、
タイブレーカーが追加された
CWG 1406 C++11 非静的メンバ関数テンプレートに追加された新しい最初のパラメータの
型が、そのテンプレートのref-qualifierに関連していなかった
ref-qualifierが && の場合、
型は右辺値参照型
となる
CWG 1446 C++11 ref-qualifierなしの非静的メンバ関数テンプレートに追加された
新しい最初のパラメータの型が左辺値参照型であり、
最初のパラメータが右辺値参照型の関数テンプレートと
比較される場合でもそうであった
この場合、型は
右辺値参照型
となる
CWG 2373 C++98 部分順序付けにおいて静的メンバ関数テンプレートの
パラメータリストに新しい最初のパラメータが追加されていた
追加されない

関連項目