テンプレートの実引数推定
関数テンプレートを実体���するためには、すべてのテンプレート実引数が判明していなければなりませんが、すべてのテンプレート実引数を指定する必要はありません。 可能なときは、足りないテンプレート実引数をコンパイラが関数の実引数から推定します。 これは関数の呼び出しが試みられたとき、関数テンプレートのアドレスが取られたとき、およびその他のいくつかの文脈で行われます。
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) を実体化します。 }
この仕組みにより、テンプレート演算子が使用できるようになります。 関数呼び出し式として書き換える以外に、演算子にテンプレート実引数を指定する構文は無いためです。
テンプレートの実引数推定は、関数テンプレートの名前解決 (これは実引数依存の名前探索を発生させることがあります) の後、かつテンプレートの実引数置換 (これは SFINAE を発生させることがあります) およびオーバーロード解決の前、に行われます。
テンプレートの実引数推定はクラステンプレートの名前が構築するオブジェクトの型として使用されたときにも行われます。 std::pair p(2, 4.5); std::tuple t(4, 3, 2.5); std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); auto lck = std::lock_guard(foo.mtx); std::lock_guard lck2(foo.mtx, ul); クラステンプレートに対するテンプレートの実引数推定は、宣言で、および明示的なキャスト式で行われます。 詳細についてはクラステンプレートの実引数推定を参照してください。 |
(C++17以上) |
目次 |
[編集] 関数呼び出しからの推定
A
の推定された型を生成するためにそれぞれの仮引数 P
に置き換えることができるテンプレートの実引数 (型テンプレート仮引数 T
i に対しては型、テンプレートテンプレート仮引数 TT
i に対してはテンプレート、非型テンプレート仮引数 I
に対しては値) を決定するために、テンプレートの実引数推定が試みられます。 推定された型は、後述の調節後、実引数 A
の型と同じになります。
複数の仮引数がある場合は、それぞれの P
と A
の組が別々に推定され、その後、それらの推定されたテンプレート実引数が組み合わされます。 推定が失敗したか、いずれかの P
と A
の組について曖昧であるか、異なる組が異なるテンプレート実引数に推定されるか、いずれかのテンプレート実引数が推定されず明示的に指定もされずに残されている場合、コンパイルは失敗します。
template<class T> void f(std::initializer_list<T>); f({1, 2, 3}); // P = std::initializer_list<T>, A = {1, 2, 3} // P'1 = T, A'1 = 1: T の推定結果 = int // P'2 = T, A'2 = 2: T の推定結果 = int // P'3 = T, A'3 = 3: T の推定結果 = int // OK、 T の推定結果 = int f({1, "abc"}); // P = std::initializer_list<T>, A = {1, "abc"} // P'1 = T, A'1 = 1: T の推定結果 = int // P'2 = T, A'2 = "abc": T の推定結果 = const char* // エラー、推定失敗。 T は曖昧です。
template<class T, int N> void h(T const(&)[N]); h({1, 2, 3}); // T の推定結果 = int, N = 3 template<class T> void j(T const(&)[3]); j({42}); // T の推定結果 = int (配列のサイズは仮引数でないため考慮されません) struct Aggr { int i; int j; }; template<int N> void k(Aggr const(&)[N]); k({1, 2, 3}); // エラー、推定失敗。 int から Aggr への変換は存在しません。 k({{1}, {2}, {3}}); // OK、 N の推定結果 = 3 template<int M, int N> void m(int const(&)[M][N]); m({{1, 2}, {3, 4}}); // M の推定結果 = 2, N の推定結果 = 2 template<class T, int N> void n(T const(&)[N], T); n({{1}, {2}, {3}}, Aggr()); // T の推定結果 = Aggr、 N の推定結果 = 3 最後の template<class... Types> void f(Types&...); void h(int x, float& y) { const int z = x; f(x, y, z); // P = Types&..., A1 = x: Types... の1つめの推定結果 = int // P = Types&..., A2 = y: Types... の2つめの推定結果 = float // P = Types&..., A3 = z: Types... の3つめの推定結果 = const int // f<int, float, const int> を呼びます。 }
|
(C++11以上) |
P
が関数型、関数ポインタ型、またはメンバ関数ポインタ型の場合、かつ、 A
がオーバーロードされた関数の集合で関数テンプレートを含まない場合は、テンプレートの実引数推定はそれぞれのオーバーロードに対して試みられます。 そのうちひとつだけが成功した場合は、その成功した推定が使用されます。 成功したものが無かったか複数あった場合は、そのテンプレート仮引数は非推定文脈です (後述)。
template<class T> int f(T(*p)(T)); int g(int); int g(char); f(g); // P = T(*)(T), A = オーバーロード集合 // P = T(*)(T), A1 = int(int): T の推定結果 = int // P = T(*)(T), A2 = int(char): T の推定に失敗 // オーバーロードのうちひとつだけが上手くいったので、推定は成功です。
推定が始まる前に、 P
と A
に以下の調節が行われます。
P
が参照型でない場合、A
が配列型の場合、 A
は配列からポインタへの変換によってポインタ型に置き換えられます。A
が関数型の場合、 A
は関数からポインタへの変換によってポインタ型に置き換えられます。A
が cv 修飾された型の場合、トップレベルの cv 修飾は推定のためには無視されます。
template<class T> void f(T); int a[3]; f(a); // P = T, A = int[3] (int* に調節されます): T の推定結果 = int* void b(int); f(b); // P = T, A = void(int) (void(*)(int) に調節されます): T ���推定結果 = void(*)(int) const int c = 13; f(c); // P = T, A = const int (int に調節されます): T の推定結果 = int
P
が cv 修飾された型の場合、トップレベルの cv 修飾は推定のためには無視されます。P
が参照型の場合、推定のためには P
の参照先の型が使用されます。P
が cv 修飾されていないテンプレート仮引数への右辺値参照 (いわゆる転送参照) であり、対応する関数呼び出しの実引数が左辺値の場合、推定のためには A
の代わりに A
への左辺値参照型が使用されます (ノート: これが std::forward の動作の原理です) (ノート: クラステンプレートの実引数推定でクラステンプレートのテンプレート仮引数が転送参照となることはありません) (C++17以上)。
template<class T> int f(T&&); // P は cv 修飾されていない T への右辺値参照 (転送参照) です。 template<class T> int g(const T&&); // P は cv 修飾されている T への右辺値参照 (特別な効果はありません) です。 int main() { int i; int n1 = f(i); // 実引数が左辺値である: f<int&>(int&) を呼びます (特別なケース)。 int n2 = f(0); // 実引数が左辺値でない: f<int>(int&&) を呼びます。 // int n3 = g(i); // エラー、 g<int>(const int&&) に推定されますが、 // 右辺値参照は左辺値に束縛できません。 }
これらの変換の後、推定は型からの推定で述べるように処理され、変換された A
(つまり前述の調節後の A
) に等しい、推定された A
(つまり前述の調節および推定されたテンプレート仮引数の置き換え後の P
) が得られるような、テンプレート実引数の発見を試みます。
P
および A
からの通常の推定が失敗した場合は、以下の内容が追加で考慮されます。
P
が参照型の場合、推定された A
(すなわち参照先の型) は変換された A
より多く cv 修飾されていても構いません。
template<typename T> void f(const T& t); bool a = false; f(a); // P = const T& (const T に調節されます), A = bool: // T の推定結果 = bool、 A の推定結果 = const bool // 推定された A は元の A より多く cv 修飾されています。
A
は、修飾変換または関数ポインタ変換 (C++17以上)によって推定された A
に変換可能な、別のポインタまたはメンバポインタ型でも構いません。
template<typename T> void f(const T*); int* p; f(p); // P = const T*, A = int*: // T の推定結果 = int、 A の推定結果 = const int* // 修飾変換 (int* → const int*) が適用されています。
P
がクラスであり、 P
が単純テンプレート識別子の形式で場合、変換された A
は推定された A
の派生クラスであっても構いません。 同様に、 P
が単純テンプレート識別子の形式のクラスへのポインタである場合、変換された A
は推定された A
が指す先のクラスの派生クラスへのポインタであっても構いません。
template<class T> struct B { }; template<class T> struct D : public B<T> { }; template<class T> void f(B<T>&) { } void f() { D<int> d; f(d); // P = B<T>& (P = B<T> に調節されます (これは単純テンプレート識別子です)), A = D<int>: // T の推定結果 = int、 A の推定結果 = B<int> // 元の A は推定された A から派生しています。 }
[編集] 非推定文脈
以下の場合、 P
を構成するために使用される型、テンプレート、および非型の値は、テンプレートの実引数推定に寄与しません。 代わりに、他の場所で推定されたか明示的に指定されたテンプレート実引数を使用します。 テンプレート仮引数が非推定文脈のみで使用されていて、明示的に指定されていない場合、テンプレートの実引数推定は失敗します。
// identity テンプレート。 特定の実引数を推定から除外するためにしばしば使われます。 // (C++20 では std::type_identity として利用可能です) template<typename T> struct identity { typedef T type; }; template<typename T> void bad(std::vector<T> x, T value = 1); template<typename T> void good(std::vector<T> x, typename identity<T>::type value = 1); std::vector<std::complex<double>> x; bad(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: T の推定結果 = std::complex<double> // P2 = T, A2 = double // P2/A2: T の推定結果 = double // エラー、推定失敗。 T は曖昧です。 good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: T の推定結果 = std::complex<double> // P2 = identity<T>::type, A2 = double // P2/A2: T は :: の左のため非推定です。 P1/A1 によって推定された T を使用します。 // OK、 T = std::complex<double>
2) decltype 指定子の式。
template<typename T> void f(decltype(*std::declval<T>()) arg); int n; f<int*>(n); // P = decltype(*declval<T>()), A = int: T は非推定文脈です。 |
(C++11以上) |
template<std::size_t N> void f(std::array<int, 2 * N> a); std::array<int, 10> a; f(a); // P = std::array<int, 2 * N>, A = std::array<int, 10>: // 2 * N は非推定文脈です。 N は推定できません。 // 注: f(std::array<int, N> a) であれば N を推定できます。
template<typename T, typename F> void f(const std::vector<T>& v, const F& comp = std::less<T>()); std::vector<std::string> v(3); f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> の左辺値 // P1/A1: T の推定結果 = std::string // P2 = const F&, A2 = std::less<std::string> の右辺値 // P2 は、 f(v) の呼び出しで使用中のデフォルト引数を持つ関数引数 comp // の仮引数の型 (const F&) で使用されている F (テンプレート仮引数) // について、非推定文脈です。
P
にマッチするか、 P
にマッチする関数がないか、オーバーロード集合が関数テンプレートを1つ以上含むような関数またはオーバーロード集合である A
に対応する仮引数 P
。
6) A が波括弧初期化子リストである仮引数 P 。 ただし、 P が std::initializer_list であるか、その参照 (cv 修飾されていても構いません) であるか、配列への参照である場合は除きます。
template<class T> void g1(std::vector<T>); template<class T> void g2(std::vector<T>, T x); g1({1, 2, 3}); // P = std::vector<T>, A = {1, 2, 3}: T は非推定文脈です。 // エラー、 T は明示的に指定されておらず、別の P/A からも推定されません。 g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T は非推定文脈です。 // P2 = T, A2 = int: T の推定結果 = int |
(C++11以上) |
P
。
template<class... Ts, class T> void f1(T n, Ts... args); template<class... Ts, class T> void f2(Ts... args, T n); f1(1, 2, 3, 4); // P1 = T, A1 = 1: T の推定結果 = int // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: Ts の推定結果 = [int, int, int] f2(1, 2, 3, 4); // P1 = Ts...: Ts は非推定文脈です
P
内に現れるテンプレート仮引数リスト。 テンプレート仮引数リストの最後でないパック展開を含みます。
template<int...> struct T { }; template<int... Ts1, int N, int... Ts2> void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&); template<int... Ts1, int N, int... Ts2> void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&); T<1, 2> t1; T<1, -1, 0> t2; good(t1, t2); // P1 = const T<N, Ts1...>&, A1 = T<1, 2>: // N の推定結果 = 1、 Ts1 の推定結果 = [2] // P2 = const T<N, Ts2...>&, A2 = T<1, -1, 0>: // N の推定結果 = 1、 Ts2 の推定結果 = [-1, 0] bad(t1, t2); // P1 = const T<Ts1..., N>&, A1 = T<1, 2>: // <Ts1..., N> は非推定文脈です // P2 = const T<Ts2..., N>&, A2 = T<1, -1, 0>: // <Ts2..., N> は非推定文脈です
P
が配列型 (配列への参照や配列へのポインタは含みません) の場合、その最初の次元。
template<int i> void f1(int a[10][i]); template<int i> void f2(int a[i][20]); // P = int[i][20] (配列型) template<int i> void f3(int (&a)[i][20]); // P = int(&)[i][20] (配列への参照) void g() { int a[10][20]; f1(a); // OK: i の推定結果 = 20 f1<20>(a); // OK f2(a); // エラー、 i は非推定文脈です f2<10>(a); // OK f3(a); // OK: i の推定結果 = 10 f3<10>(a); // OK }
いずれの場合でも、型名のいずれかの部分が非推定の場合は、その型名全体が非推定文脈です。 しかし、複合型は推定型名と非推定型名の両方を含むことができます。 例えば、 A<T>::B<T2> の場合、 T
はルール #1 (ネストした名前指定子) のため非推定であり、 T2
は同じ型名の一部であるため非推定ですが、 void(*f)(typename A<T>::B, A<T>) の場合、 A<T>::B の T
は (同じルールのため) 非推定であるのに対して、 A<T> の T
は推定されます。
[編集] 型からの推定
1つ以上の型テンプレート仮引数 T
i、テンプレートテンプレート仮引数 TT
i または非型テンプレート仮引数 I
i に依存する関数仮引数 P
および対応する実引数 A
が与えられたとき、 P
が以下のいずれかの形式であれば、推定が行われます。
This section is incomplete Reason: possibly a table with micro-examples |
-
T
-
cv-list T
-
T*
-
T&
-
T&&
-
T[integer-constant]
-
class-template-name<T>
-
type(T)
-
T()
-
T(T)
-
T type::*
-
type T::*
-
T T::*
-
T(type::*)()
-
type(T::*)()
-
type(type::*)(T)
-
type(T::*)(T)
-
T (type::*)(T)
-
T (T::*)()
-
T (T::*)(T)
-
type[i]
-
class-template-name<I>
-
TT<T>
-
TT<I>
-
TT<>
ただし
-
(T)
は、少なくともひとつは T を含む、関数仮引数型リストです。 -
()
は、 T を含まない、関数仮引数型リストです。 -
<T>
は、少なくともひとつは T を含む、テンプレート実引数リストです。 -
<I>
は、少なくともひとつは I を含む、テンプレート実引数リストです。 -
<>
は、 T も I も含まない、テンプレート実引数リストです。
P
がテンプレート仮引数リスト <T>
または <I>
を含む形式のいずれかである場合、そのテンプレート仮引数リストのそれぞれの要素 P
i は、その A
の対応するテンプレート実引数 A
i に対してマッチされます。 最後の P
i がパック展開の場合は、そのパターンが A
のテンプレート実引数リスト内の残りの実引数のそれぞれに対して比較されます。 末尾のパラメータパックが他に推定されない場合は、空のパラメータパックに推定されます。
P
が関数仮引数リスト (T)
を含む形式のいずれかである場合、そのリストのそれぞれの仮引数 P
i は A
の関数仮引数リストの対応する実引数 A
i と比較されます。 最後の P
i がパック展開の場合は、その宣言子が A
の仮引数型リスト内の残りの A
i のそれぞれと比較されます。
上記の形式はネストでき、再帰的に処理されます。 例えば、 X<int>(*)(char[6])
は、 type の部分が class-template-name<T>
で T が type[i]
の、 type(*)(T)
の形式です。
型テンプレート実引数を非型テンプレート実引数の型から推定することはできません。 template<typename T, T i> void f(double a[10][i]); double v[10][20]; f(v); // P = double[10][i], A = double[10][20]: // i は 20 に等しいと推定されますが、 // T を i の型から推定することはできません。 |
(C++17未満) |
依存型を用いて宣言された非型テンプレート仮引数 P に対応する実引数の値が式から推定されるとき、 P の型内のテンプレート仮引数は値の型から推定されます。 template <long n> struct A { }; template <class T> struct C; template <class T, T n> struct C<A<n>> { using Q = T; }; typedef long R; typedef C<A<2>>::Q R; // OK、 T は型 A<2> 内のテンプレート実引数の値から // long と推定されます。 template <auto X> class bar{}; template <class T, T n> void f(bar<n> x); f(bar<3>{}); // OK、 T は型 bar<3> 内のテンプレート実引数の値から // int と推定されます (そして n は 3 と推定されます)。 型 template<class T, T i> void f(int (&a)[i]); int v[10]; f(v); // OK、 T は std::size_t です。 |
(C++17以上) |
仮引数リスト内で非型テンプレート仮引数が使用され、対応するテンプレート実引数が推定される場合、推定されたテンプレート実引数の型は非型テンプレート実引数の型 (その囲っているテンプレート仮引数リストで指定された通りの、つまり参照は維持されます) に正確にマッチしなければなりません。 ただし、 cv 修飾子は削除されます。 また、テンプレート実引数が配列のサイズから推定される場合は、任意の整数型 (たとえ bool でも (その場合は常に true になります)) が使用できます。
template<int i> class A { }; template<short s> void f(A<s>); // 非型テンプレート仮引数の型は short です。 void k1() { A<1> a; // 非型テンプレート仮引数の型は int です。 f(a); // P = A<(short)s>, A = A<(int)1> // エラー、推定された非型テンプレート実引数は // 対応するテンプレート実引数と同じ型でありません。 f<1>(a); // OK、テンプレート実引数は推定されず、 // f<(short)1>(A<(short)1>) を呼びます。 } template<int&> struct X; template<int& R> void k2(X<R>&); int n; void g(X<n> &x) { k2(x); // P = X<R>, A = X<n> // 仮引数の型は int& です。 // 実引数の型は構造体 X のテンプレート宣言の int& です。 // OK (CWG 2091 適用済みの場合)、 R は n への参照に推定されます。 }
型テンプレート仮引数を関数のデフォルト引数の型から推定することはできません。
template<typename T> void f(T = 5, T = 7); void g() { f(1); // OK、 f<int>(1, 7) を呼びます。 f(); // エラー、 T を推定できません。 f<int>(); // OK、 f<int>(5, 7) を呼びます。 }
テンプレートテンプレート仮引数の推定は関数呼び出しで使用されたテンプレート特殊化で使用された型を使用できます。
template<template<typename> class X> struct A { }; // A はテンプレートテンプレート仮引数を持つテンプレートです。 template<template<typename> class TT> void f(A<TT>) { } template<class T> struct B { }; A<B> ab; f(ab); // P = A<TT>, A = A<B>: TT の推定結果 = B。 f(A<B>) を呼びます。
[編集] その他の文脈
関数呼び出しおよび演算子式の他に、テンプレートの実引数推定は以下の状況で使用されます。
[編集] auto 型の推定
変数の宣言で、 auto 指定子の意味を変数の初期化子から推定するとき、テンプレートの実引数推定が使用されます。
仮引数 P
は以下のように取得されます。 auto
を含む変数の宣言された型 T
内の auto
のすべての出現を、架空の型テンプレート仮引数 U
で、または、初期化子がコピーリスト初期化の場合は std::initializer_list<U>
で、置き換えます。 実引数 A
は初期化子の式です。 上で述べたルールに従って P
と A
から U
を推定した後、推定された U
を P
内に置換して実際の変数の型が取得されます。
const auto& x = 1 + 2; // P = const U&, A = 1 + 2 // f を template<class U> void f(const U& u) としたときの // f(1 + 2) を呼ぶ場合と同じルールが用いられます。 // U の推定結果 = int。 x の型は const int& です。 auto l = {13}; // P = std::initializer_list<U>, A = {13} // U の推定結果 = int。 l の型は std::initializer_list<int> です。
直接リスト初期化では (コピーリスト初期化には適用されません)、波括弧初期化子リストから auto の意味を推定するとき、波括弧初期化子リストは要素を1つだけ含まなければならず、 auto の型はその要素の型になります。
auto x1 = {3}; // x1 は std::initializer_list<int> です。 auto x2{1, 2}; // エラー、要素が1つではありません。 auto x3{3}; // x3 は int です。 // (N3922 より前では、 x2 と x3 はどちらも std::initializer_list<int> でした。)
auto を返す関数関数の宣言で、関数の戻り値の型内の auto の意味を return 文から推定するとき、テンプレートの実引数推定が使用されます。 auto を返す関数の場合、仮引数 auto f() { return 42; } // P = auto, A = 42: // U の推定結果 = int。 f の戻り値の型は int です。 関数に複数の return 文がある場合は、それぞれの return 文について推定が行われます。 結果の型はすべて同じでなければならず、その結果の型が実際の戻り値の型になります。 関数に return 文がない場合は、推定時の ノート: 変数宣言および関数宣言における decltype(auto) プレーズホルダの意味はテンプレートの実引数推定を使用しません。 |
(C++14以上) |
[編集] オーバーロード解決
オーバーロード解決において、候補テンプレート関数から特殊化を生成するとき、テンプレートの実引数推定が使用されます。
P
および A
は通常の関数呼び出しの場合と同じです。
std::string s; std::getline(std::cin, s); // 「std::getline」は4つの関数テンプレートを表します。 // そのうち2つが候補関数 (引数の数が合っている) です。 // 第1候補: // P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // 推定により型テンプレート引数 CharT、 Traits、および Allocator が決定され、 // 特殊化 std::getline<char, std::char_traits<char>, std::allocator<char>> が生成されます。 // 第2候補: // P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // 推定により型テンプレート引数 CharT、 Traits、および Allocator が決定され、 // 特殊化 std::getline<char, std::char_traits<char>, std::allocator<char>> が生成されます。 // オーバーロード解決により左辺値 std::cin からの参照束縛が順位付けされ、 // 2つの候補特殊化のうちの1つめが選択されます。
推定が失敗した場合 、または推定は成功したけれども生成された特殊化が無効 (例えば仮引数がクラスでも列挙型でもないオーバーロード演算子) であった場合 (C++14以上)、その特殊化はオーバーロード集合に含まれません (SFINAE と同様です)。
[編集] オーバーロード集合のアドレス
オーバーロード集合のアドレス (関数テンプレートを含みます) を取るとき、テンプレートの実引数推定が使用されます。
関数テンプレートの関数の型が P
です。 目的の型が A
の型です。
std::cout << std::endl; // std::endl は関数テンプレートを表します。 // endl の型 P = // std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&) // operator<< の仮引数 A = // std::basic_ostream<char, std::char_traits<char>>& (*)( // std::basic_ostream<char, std::char_traits<char>>& // ) // (operator<< の他のオーバーロードは実行可能ではありません) // 推定により型テンプレート引数 CharT および Traits が決定されます。
この場合、推定に追加のルールが適用されます。 関数の引数 P
i と A
i を比較するとき、 P
i が cv 修飾されていないテンプレート仮引数への右辺値参照 (「転送参照」) であり、対応する A
i が左辺値参照である場合、 P
i はテンプレート仮引数の型に調節されます (T&& が T になります)。
関数テンプレートの戻り値の型がプレースホルダ (auto または decltype(auto)) の場合、その戻り値の型は非推定文脈であり、実体化から決定されます。 |
(C++14以上) |
[編集] 半順序
オーバーロードされた関数テンプレートの半順序付けでテンプレートの実引数推定が使用されます。
This section is incomplete Reason: mini-example |
[編集] 変換関数テンプレート
ユーザ定義変換関数のテンプレート実引数を選択するときテンプレートの実引数推定が使用されます。
A
は変換の結果として要求される型です。 P
は変換関数テンプレートの戻り値の型です。 ただし、
P
はその参照先の型です。A
が参照型でない場合、 P
は配列からポ���ンタへの変換によって取得されたポインタ型です。A
が参照型でない場合、 P
は関数からポインタへの変換によって取得された関数ポインタ型です。P
が cv 修飾されている場合は、そのトップレベルの cv 修飾は無視されます。A
が cv 修飾されている場合は、そのトップレベルの cv 修飾は無視されます。 A
が参照型の場合は、その参照先の型が推定に使用されます。
P
と A
からの通常の推定 (上で説明した通りの) が失敗した場合は、以下の内容が追加で考慮されます。
A
が参照型の場合、 A
は推定された A
より多く cv 修飾されていても構いません。A
がポインタまたはメンバポインタ型の場合、推定された A
は修飾変換によって A
に変換可能な任意のポインタであっても構いません。
struct A { template<class T> operator T***(); }; A a; const int* const* const* p1 = a; // P = T***, A = const int* const* const* // const int* const* const* 型の実引数を用いて // template<class T> void f(T*** p) が呼ばれた場合に対する // 通常の関数呼び出しの推定は失敗します。 // 変換関数のための追加の推定により T = int が決定されます // (推定された A は int*** であり、これは const int* const* const* に変換可能です)。
c)
A が関数ポインタ型の場合、推定された A は関数ポインタ変換によって A に変換可能な noexcept 関数へのポインタであっても構いません。d) A がメンバ関数ポインタの場合、推定された A は関数ポインタ変換によって A に変換可能な noexcept メンバ関数へのポインタであっても構いません。
|
(C++17以上) |
変換関数テンプレートに関する他のルールについてはメンバテンプレートを参照してください。
[編集] 明示的実体化
明示的実体化、明示的特殊化、および declarator-id が関数テンプレートの特殊化を参照するフレンド宣言 (例えば friend ostream& operator<< <> (...)) で、明示的に指定されておらずデフォルト化もされていないテンプレート実引数がある場合、どのテンプレートの特殊化が参照されているかを決定するために、テンプレートの実引数推定が使用されます。
P
はマッチする可能性を考慮され中の関数テンプレートの型で、 A
は宣言された関数の型です。 (半順序付けの後に) マッチが無いか複数ある場合は、その関数宣言は ill-formed です。
template<class X> void f(X a); // 1つめのテンプレート f template<class X> void f(X* a); // 2つめのテンプレート f template<> void f<>(int* a) { } // f の明示的実体化 // P1 = void(X), A1 = void(int*): X の推定結果 = int*、すなわち f<int*>(int*) // P2 = void(X*), A2 = void(int*): X の推定結果 = int、すなわち f<int>(int*) // そして f<int*>(int*) と f<int>(int*) が半順序付けされ、 // より特殊化されたテンプレートとして f<int>(int*) が選択されます。
この場合、推定に追加のルールが適用されます。 関数の引数 P
i と A
i を比較するとき、 P
i が cv 修飾されていないテンプレート仮引数への右辺値参照 (「転送参照」) であり、対応する A
i が左辺値参照である場合、 P
i はテンプレート仮引数の型に調節されます (T&& が T になります)。
[編集] 解放関数テンプレート
配置形式の operator new
に解放関数テンプレートの特殊化がマッチするかどうかを判定するとき、テンプレートの実引数推定が使用されます。
P
はマッチする可能性を考慮され中の関数の型で、 A
は考慮中の配置 operator new にマッチする解放関数の関数の型です。 (オーバーロード解決の後に) マッチが無いか複数ある場合は、その配置解放関数は呼ばれません (メモリリークが発生する可能性があります)。
struct X { X() { throw std::runtime_error(""); } static void* operator new(std::size_t sz, bool b) { return ::operator new(sz); } static void* operator new(std::size_t sz, double f) { return ::operator new(sz); } template<typename T> static void operator delete(void* ptr, T arg) { ::operator delete(ptr); } }; int main() { try { X* p1 = new (true) X; // X() が例外を投げた場合、 operator delete が名前探索されます。 // P1 = void(void*, T), A1 = void(void*, bool): // T の推定結果 = bool // P2 = void(void*, T), A2 = void(void*, double): // T の推定結果 = double // オーバーロード解決により operator delete<bool> が選択されます。 } catch(const std::exception&) { } try { X* p1 = new (13.2) X; // 同様に名前探索され、 operator delete<double> が選択されます。 } catch(const std::exception&) { } }
[編集] エイリアステンプレート
エイリアステンプレートは推定されません。
template<class T> struct Alloc { }; template<class T> using Vec = vector<T, Alloc<T>>; Vec<int> v; template<template<class, class> class TT> void g(TT<int, Alloc<int>>); g(v); // OK: TT の推定結果 = vector template<template<class> class TT> void f(TT<int>); f(v); // エラー、 Vec はエイリアステンプレートであるため TT は「Vec」として推定できません。
[編集] 暗黙の変換
型推定は、上で述べた型の調節を除いて、暗黙の変換を考慮しません。 暗黙の変換は後に発生するオーバーロード解決の仕事です。
しかし、テンプレートの実引数推定に参加するすべての仮引数について推定が成功し、推定されないすべてのテンプレート実引数が明示的に指定されているかデフォルト化されている場合は、残りの関数仮引数は対応する関数実引数と比較されます。 明示的に指定されたテンプレート実引数の置換の前に日依存であった型を持つ残りの仮引数 テンプレートの実引数推定に参加するテンプレート仮引数のない依存型を持つ仮引数、および、明示的に指定されたテンプレート実引数の置換により非依存になった仮引数は、オーバーロード解決中にチェックされます。 template<class T> struct Z { typedef typename T::x xx; }; template<class T> typename Z<T>::xx f(void*, T); // #1 template<class T> void f(int, T); // #2 struct A { } a; int main() { f(1, a); // #1 について、推定により T = struct A が決定されますが、残りの実引数 1 は // 対応する仮引数 void* に暗黙に変換できません。 推定は失敗です。 // 戻り値の型の実体化は要求されません。 // #2 について、推定により T = struct A が決定され、残りの実引数 1 は // 対応する仮引数 int に暗黙に変換可能です。 推定は成功です。 // この関数呼び出しは #2 の呼び出しとしてコンパイルされます (推定失敗は SFINAE です)。 } |
(C++14以上) |
[編集] 欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
DR | 適用先 | 発行時の動作 | 正しい動作 |
---|---|---|---|
CWG 1391 | C++14 | effect of implicit conversions of the arguments that aren't involved in deduction were not specified | specified as described above |
CWG 1591 | C++11 | cannot deduce array bound and element type from a braced-init-list | deduction allowed |
CWG 2052 | C++14 | deducing an operator with non-class arguments was a hard error (in some compilers) | soft error if there are other overloads |
CWG 2091 | C++98 | deducing a reference non-type parameter did not work due to type mismatch against the argument | type mismatch avoided |
N3922 | C++11 | direct-list-initialization of auto deduces std::initializer_list
|
ill-formed for >1 element, deduce element type for single element |