パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.7.0.5(5)
初期化とオーバーロード(2)

C++規格で密に相互依存する変数初期化と関数オーバーロードのうち、関数オーバーロードの詳細を確認する。

変数初期化と関数オーバーロードについてC++17(JTC1/SC22/WG21 N4659)の規格を確認する。前編の初期化とオーバーロード(1)はその関係を概観して変数初期化を議論した。本記事は後編として暗黙的変換シーケンスと関数オーバーロードを議論する。

本記事においてオブジェクトはC++規格定義(4.5/p1)とする。他記事のほとんどで用いる"クラスとインスタンスの総称"ではないことに留意いただきたい。

暗黙的変換シーケンス

"型変換"は式の型と値カテゴリを変換する。"暗黙的変換"は、プログラマが指示する明示的変換(N4659 8.2.3、8.4)に対して、主に関数コールで行うコンパイラによる自動の型変換である。"変換シーケンス"は型変換の要素ルール0個以上を組み合わせて型変換するシーケンスとする。"暗黙的変換シーケンス(implicit conversion sequence)"は関数コールの実引数から仮引数型への変換シーケンスと定義する(16.3.3.1/p1)。組み込み演算子(built-in operator)もそういった関数コールの一つとして扱う(8/p3)。暗黙的変換シーケンスは既述したオブジェクト/参照の初期化ルールによる(7/p6)。

暗黙的変換シーケンスは以下三つに分類する。

  • 標準変換シーケンス
  • ユーザー定義変換シーケンス
  • 省略記号変換シーケンス

標準変換シーケンスとユーザー定義変換シーケンスは初期化の過程を再分類する。再分類は関数オーバーロードが複数の暗黙的変換シーケンスを比較する場合の"優劣"を定義するために必要となる。つまり初期化規格(11.6)と暗黙的変換シーケンス規格(16.3.3.1)は同じ事象の表裏を定め、互いに矛盾しない(はずだ)。省略記号変換シーケンスは仮引数に省略記号(...)を含む関数へのコールに現れる(16.3.3.1.3/p1)が、これはこれで厄介な存在なので詳細に触れない。

暗黙的変換シーケンスは、仮引数型が参照の場合は後述に従い、参照でなければ実引数を仮引数型の純右辺値式に変換して仮引数をコピー初期化する(16.3.3.1/p6)。トップレベルのcv修飾子は変換に関与しない。代入演算子左側の被演算子式の暗黙的変換シーケンスは標準変換シーケンスのみ許される(p7)。実引数の型が仮引数型に一致すれば暗黙的変換シーケンスは無変換で(p8)、一致できる変換シーケンスが存在しなければ変換できない(p9)。複数の一致できる変換シーケンスが存在して優劣に差が無ければ曖昧変換シーケンス(ambiguous conversion sequence)で一つのユーザー定義変換シーケンスとして扱い、コールが結果としてこれを選択すれば曖昧エラーとなる(p10)。

標準変換シーケンス

標準変換シーケンスは、左辺値変換カテゴリ、修飾修正カテゴリ、拡張/変換カテゴリの順に、それぞれに属する0~1個の標準変換を行う(7/p1、16.3.3.1.1/p2)。

標準変換 カテゴリ ランク N4659
無変換(No conversion required) 同一(Identity) 完全一致(Exact Match)
左辺値から右辺値へ変換(Lvalue-to-rvalue conversion) 左辺値変換(Lvalue Transformation) 7.1
配列からポインタへ変換(Array-to-pointer conversion) 7.2
関数からポインタへ変換(Function-to-pointer conversion) 7.3
修飾変換(Qualification conversions) 修飾修正(Qualification Adjustment) 7.5
関数ポインタ変換(Function pointer conversion) 7.13
整数拡張(Integral promotion) 拡張(Promotion) 拡張(Promotion) 7.6
浮動小数点拡張(Floating-point promotion) 7.7
整数変換(Integral conversions) 変換(Conversion) 変換(Conversion) 7.8
浮動小数点変換(Floating-point conversions) 7.9
浮動小数点整数変換(Floating-integral conversions) 7.10
ポインタ変換(Pointer conversions) 7.11
ポインタからメンバ変換(Pointer to member conversions) 7.12
ブール値変換(Boolean conversions) 7.14

左辺値変換カテゴリは式の値カテゴリを純右辺値式に変換し、その他のカテゴリは純右辺値式の型を変換する。標準変換シーケンスのランクは構成する標準変換の最も劣るランクに従う。ランクを優れる方から並べれば、完全一致ランク、拡張ランク、変換ランクである(16.3.3.2/p4)。

参照でないクラス型の変換前後がcv修飾を除いて同じ型であれば無変換とする(16.3.3.1/p6)。変換後が基底クラスの場合は"派生から基底変換"(derived-to-base conversion)として変換カテゴリとする。派生から基底変換は標準変換の一つと見なさないが、標準変換シーケンスを構成するものとしてユーザー定義変換ではない。

void f1() {}
void f2() noexcept {}
struct B {int i_;};
struct D:B {};
void f_ExactMatchRank(int,int,int*,void(*)(),const int*,void(*)()) {}
void f_PromotionRank(int,double) {}
void f_ConversionRank(int,double,int,B*,int D::*,bool) {}
void f_ClassConversions(D,B) {}
int main()
{
int i{0};
int a[10];
D c{};
f_ExactMatchRank // 完全一致ランク
(0 // int→int 無変換
,i // 左辺値式→右辺値式 左辺値から右辺値へ変換
,a // int[10]→int* 配列からポインタへ変換
,f1 // void()→void(*)() 関数からポインタへ変換
,&i // int*→const int* 修飾変換
,&f2 // void(*)() noexcept→void(*)() 関数ポインタ変換
);
f_PromotionRank // 拡張ランク
(char(0) // char→int 整数拡張
,short(0) // short→double 浮動小数点拡張
);
f_ConversionRank // 変換ランク
((unsigned int)(0) // unsigned int→int 整数変換
,(long double)(0) // long double→double 浮動小数点変換
,double(0) // double→int 浮動小数点整数変換
,&c // D*→B* ポインタ変換
,&B::i_ // int B::*→int D::* ポインタからメンバ変換
,0 // int→bool ブール値変換
);
f_ClassConversions
(c // 完全一致ランク D→D 無変換
,c // 変換ランク D→B 派生から基底変換
);
return 0;
}

ユーザー定義変換シーケンス

ユーザー定義変換とは変換コンストラクタ(converting constructor)あるは変換関数(conversion function)によるオブジェクトの型変換(15.3/p1)である。S型からT型へ型変換するものとして、変換コンストラクタ(15.3.1/p1)は例えばexplicitでないT::T(S)であり、変換関数(15.3.2/p1)は例えばS::operator T()であり、すなわちSとTの少なくとも一方はクラス型でなければならない。ユーザー定義変換シーケンスは、第一の標準変換シーケンス、ユーザー定義変換、第二の標準変換シーケンスの順に行う(16.3.3.1.2/p1-2)。変換後の型が参照となる場合は後述する。第一の標準変換シーケンスが変換前の型(と値カテゴリ)をS(の右辺値式)に変換し、第二の標準変換シーケンスがTを変換後の型に変換する。標準変換シーケンスは無変換(つまり何もしない)かもしれない。標準変換シーケンスはクラス型と非クラス型の相互変換できず、すなわち変換前後の少なくとも一方はクラス型でなければならない。

覚え書き
規格はユーザー定義変換シーケンス(16.3.3.1.2)の最後の段落(p4)で変換後が同じか基底クラスの場合を記述する。コールされるコピーコンストラクタは非explicitであれば変換コンストラクタの一つでもある。これらから、変換後が同じか基底クラスの場合もユーザー定義変換であると理解するかもしれないが、それは誤りだ。ユーザー定義変換シーケンスの項目中ではあるが、段落内の記述にユーザー定義変換(user-defined conversion)という用語はどこにも示されていない。繰り返すが、これらは標準変換シーケンスを構成するものとしてユーザー定義変換ではない。

実引数が初期化式の場合(つまり{...}でない場合)、変換先がコンストラクタの第一仮引数であるか変換関数の暗黙オブジェクト仮引数で、そのコンストラクタあるいは変換関数がユーザー定義変換の関数候補となる場合、ユーザー定義変換シーケンスは考慮しない(16.3.3.1/p4)。これが"暗黙のユーザー定義変換は1レベルのみ許される"として広く知られるルール(Bjarne Stroustrup, C++ Programming Language Third Edition, Reading, Addison-Wesley, 1997, p.276)を規定する。C++03以前の変換コンストラクタは1個の実引数でコール可能な非explicitなコンストラクタであったが、C++11以降は単に非explicitなコンストラクタに改めてられていて、任意要素数の実引数{...}もコールできる。{...}はa(あるいは空)で変換コンストラクタをオーバーロード選択するが、aの要素はユーザー定義変換シーケンスを考慮できる(16.3.3.1.5/p6)。つまりaが単独要素なら実引数{a}として暗黙のユーザー定義変換を2レベル行え、さらに{...}をネストすればその分だけ変換できる。例外はa自体も単独要素の初期化リスト、例えば{a1}とすれば実引数{{a1}}で、変換先がクラスXコンストラクタの第一仮引数でXまたはその参照型である場合、ユーザー定義変換シーケンスは考慮しない。さもなくば、X(a1)による初期化とX(a1)でユーザー定義変換した一時オブジェクトからのコピーが曖昧となる。

struct A // intとconst E&からの変換コンストラクタとintへの変換関数を持つ
{
A() {}
A(int) {}
A(const struct E&) {}
operator int() const {return 0;}
};
void f(A) {};
void g(int) {};
void h(double) {};
struct B:A {}; // Aの派生クラス
struct C // Bへの変換関数を持つクラス
{
operator B() const {return B{};}
};
struct D:C {}; // Cの派生クラス
struct E // const G&からの変換コンストラクタを持つ
{
E() {}
E(const struct G&) {}
};
struct F:E // Eの派生クラス、Aへの(constメンバでない)変換関数を持つ
{
operator A() {return A{};}
};
struct G {};
int main()
{
// 標準変換シーケンス
f(B{}); // B→A(派生から基底変換)
// ユーザー定義変換シーケンス(第一の標準変換シーケンス、ユーザー定義変換、第二の標準変換シーケンス)
f(0); // 無変換、int→A(変換コンストラクタ)、無変換
f(0.0); // double→int(浮動小数点整数変換)、int→A(変換コンストラクタ)、無変換
g(A{}); // 無変換、A→int(変換関数)、無変換
h(A{}); // 無変換、A→int(変換関数)、int→double(浮動小数点整数変換)
f(D{}); // D→C(派生から基底変換)、C→B(変換関数)、B→A(派生から基底変換)
f(E{}); // 無変換、E→A(変換コンストラクタ)、無変換
f(F{}); // 無変換、F→A(変換関数)、無変換
f((const F)F{}); // const F→const E(派生から基底変換)、const E→A(変換コンストラクタ)、無変換
// 暗黙の2レベルユーザー定義変換の可否
//f(G{}); // 不可
f({G{}}); // 可
return 0;
}

参照型の暗黙的変換シーケンス

仮引数型が参照の場合は以下とする(16.3.3.1.4)。実引数に直接バインド(11.6.3)する場合、実引数の型が同じなら無変換(identity conversion)とする(16.3.3.1.4/p1)。直接バインドする場合で、仮引数型がクラスへの参照で実引数の型がその派生なら、派生から基底変換(derived-to-base conversion)で変換ランクとする。変換関数の結果に直接バインドする場合、ユーザー定義変換シーケンスとして第二の標準変換シーケンスは無変換あるいは基底型に変換する。直接バインドしない場合、実引数を参照される型に変換してバインドするが、これは参照される型の一時変数をコピー構築する事に相当する(p2)。標準変換シーケンスはconstでない左辺値参照を右辺値式にバインドできず、右辺値参照を左辺値式にバインドできない(p3)。

struct A // intとconst E&からの変換コンストラクタを持つ
{
A() {}
A(int) {}
A(const struct E&) {}
};
void f(const A&) {};
void g(A&) {};
void h(A&&) {};
struct B:A {}; // Aの派生クラス
struct C // Bへの変換関数を持つクラス
{
operator B() const {return B{};}
};
struct D:C {}; // Cの派生クラス
struct E {};
struct F:E // Eの派生クラス、Aへの(constメンバでない)変換関数を持つ
{
operator A() {return A{};}
};
int main()
{
A a;
f(a);g(a); // A&&に左辺値式はバインドできない
f(A{});h(A{}); // A&に右辺値式はバインドできない
// 全ての場合において、変換結果が純右辺値式であれば一時実体化変換してバインドする
// const A&に右辺値式で直接バインド、標準変換シーケンス
f(A{}); // 無変換(完全一致ランク)
f(B{}); // B→A(派生から基底変換)(変換ランク)
// const A&に右辺値式で直接バインド、ユーザー定義変換シーケンス
f(D{}); // D→C(派生から基底変換)、C→B(変換関数)、B→A(派生から基底変換)
f(F{}); // 無変換、F→A(変換関数)、無変換
// const A&に右辺値式で間接バインド、ユーザー定義変換シーケンス
f(0); // 無変換、int→A(変換コンストラクタ)、無変換
f(E{}); // 無変換、E→A(変換コンストラクタ)、無変換
f((const F)F{}); // const F→const E(派生から基底変換)、const E→A(変換コンストラクタ)、無変換
return 0;
}

リスト初期化シーケンス

実引数に現れるリスト初期化{...}は式でなく特別のルール(11.6.4)で仮引数型に変換する(16.3.3.1.5/p1)。仮引数型をTとする。{a}の初期化リストはaで、{}は初期化リストを持たない。

Tが集約クラス型で{a}のaが単一要素のTあるいは派生型であればTに変換する(p2)。Tが文字型配列で{a}のaが単一要素の文字列リテラルであれば無変換とする(p3)。配列型は仮引数型にならないので参照型に参照される場合となる(脚注133)。Tがstd::initializer_list<E>で、{a}であれば変換は各要素変換の最も劣るランクとして、{}であれば無変換として、ユーザー定義変換となりうる(p4)。Tが配列型であれば変換は各要素変換の最も劣るランクとする(p5)。Tが集約でないクラス型であれば最優関数となる変換コンストラクタをオーバーロード選択し、それが複数なら曖昧変換シーケンスで、それがTあるいは派生型であれば完全一致ランクあるいは変換ランクで、そうでなければ(初期化リストコンストラクタを選択する場合を含み)ユーザー定義変換シーケンスとして第二の標準変換シーケンスは無変換とする(p6)。この場合初期化リストの要素は、既述の{{a1}}がクラスXコンストラクタのX型第一仮引数を初期化する場合を除いて、ユーザー定義変換できる。Tが集約クラス型で集約初期化ルール(9.4.2)であれば、ユーザー定義変換シーケンスとして第二の標準変換シーケンスは無変換とする(p7)。Tが参照型の場合は参照の定義に従う(p8)。Tがクラス型、配列型、参照型のいずれでもなければ、{a}のaが単一要素でそれ自体が初期化リストでなければそれをTに変換し、{}であれば無変換とする(p9)。

#include <vector>
struct A // 集約クラス型
{
int i1_;
struct {int i2_;int i3_;} s_;
};
struct B // 集約でないクラス型
{
B() {}
B(int i1,int i2,int i3) {}
};
struct C // 変換関数を持つクラス型
{
operator B() const {return {4,5,6};} // B型への変換関数
operator int() const {return 0;} // int型への変換関数
};
void f1(A) {};
void f2(const char(&)[15]) {} // 配列型は参照型で参照する
//void f2(const char(&)[]) {} // C++20以降(N4849 7.3.5/p2)
void f3(std::vector<int>) {} // vector<int>は初期化リストコンストラクタを持つ
void f4(int(&&)[3]) {} // 配列型は参照型で参照する
void f5(B) {}
void f6(int) {}
// 仮引数がTの配列型はTのポインタ型に修正される(N4659 11.3.5/p5)
void g2(const char[15]) {} // 仮引数型const char*に修正される
void g4(int[3]) {} // 仮引数型int*に修正される
int main()
{
// 標準変換シーケンス
f1({A{}}); // 無変換
f2({"string literal"}); // 無変換
f5({B{}}); // 無変換
f6({0}); // 無変換
g2({"string literal"}); // const char[15]->const char*(配列からポインタへ変換)
//g4({'0',0,C{}}); // {...}の...は配列でないのでポインタへ変換できない
// ユーザー定義変換シーケンス
f1({1,{2,3}}); // 無変換、A(集約クラス)を{...}で初期化、無変換
f3({'0',0,C{}}); // 無変換、initializer_list<int>→vector<int>(変換コンストラクタ)、無変換
// ここでのC→int(変換関数)は許される(N4659 16.3.3.1.5/p6)
f4({'0',0,C{}}); // 無変換、C→int(変換関数)+int(&&)[3]を{...}で初期化、無変換
f5({C{}}); // 無変換、C→B(変換関数)、無変換
f5({0,1,2}); // 無変換、B(非集約クラス)を{...}で初期化、無変換
f6({C{}}); // 無変換、C→int(変換関数)、無変換
return 0;
}

暗黙的変換シーケンスの優劣

暗黙的変換シーケンスの優劣は、関数オーバーロードが一つの実引数を仮引数型に変換する場合に可能な暗黙的変換シーケンスの複数から一つを選択する基準を定義する(16.3.3.2/p1)。つまり後述のICSi(F)の選択基準にあたる。

標準変換シーケンスはユーザー定義変換シーケンスより優れ、ユーザー定義変換シーケンスは省略記号変換シーケンスより優れる(p2)。

リスト初期化シーケンスL1とL2で、L1がL2より優れる条件は(p3.1)、

  • L1がstd::initializer_list<X>に変換できるがL2はできない、あるいは
  • L1/L2が要素数N1/N2のT型配列へ変換できてN1がN2より小さい。

標準変換シーケンスS1とS2で、S1がS2より優れる条件は(p3.2)、

  • S1がS2のサブシーケンスである(ただし左辺値変換は考慮に入れない)、さもなくば
  • S1のランクがS2より優れる、さもなくば、
  • S1とS2が参照バインドでS1が右辺値参照にバインドされてS2が左辺値参照にバインドされる、さもなくば、
  • S1とS2が修飾変換のみを違えてT1型とT2型に変換する時、T1のcv修飾子がT2のそれのサブセットである、さもなくば、
  • S1とS2が参照バインドで参照型がトップレベルcv修飾子のみを違える時、S2が初期化する参照型がS1のそれより多くcv修飾される。

ユーザー定義変換シーケンスU1とU2で両者が同じ変換関数/変換コンストラクタを持つ、あるいは同じ集約クラスを初期化する場合、U1がU2よりより条件は(p3.3)、

  • U1の第二の標準変換シーケンスがU2のそれより優れる。

標準変換シーケンスの優劣はランクが定め、優れる順に完全一致ランク、拡張ランク、変換ランクとする(p4)。同じランクにある二つの変換シーケンスは以下を除き優劣が定まらない。

  • ポインタ、メンバーへのポインタ、std::nullptr_tをboolへ変換しない変換は、それらへ変換するものより優れる。
  • 基底型を持つ列挙型をその基底型へ拡張変換するのは、その基底型を拡張した型へ拡張変換するより優れる。
  • クラスBがクラスAの派生である場合、B*からA*への変換はB*からvoid*への変換より優れ、A*からvoid*への変換はB*からvoid*への変換より優れる。
  • クラスBがクラスAの派生でクラスCがクラスBの派生の場合、
    • C*からB*への変換はC*からA*への変換より優れ、
    • C型の式をBの参照にバインドするのはC型の式をAの参照にバインドするより優れ、
    • A::*からB::*への変換はA::*からC::*への変換より優れ、
    • CからBへの変換はCからAへの変換より優れ、
    • B*からA*への変換はC*からA*への変換より優れ、
    • B型の式をAの参照にバインドするのはC型の式をAの参照にバインドするより優れ、
    • B::*からC::*への変換はA::*からC::*への変換より優れ、
    • BからAへの変換はCからAへの変換より優れる。

関数オーバーロード

オーバーロード関数(overloaded function)は複数の同名関数がそれぞれ異なる仮引数宣言を持つ。関数オーバーロード(function overloading)はオーバーロード関数のどれを選択するかを対応する実引数と仮引数型を比較して決定する(N4659 16/p1-2)。決定の過程をオーバーロード解決(overload resolution)と呼び、オーバーロード関数の一つを選択する事を本サイトはオーバーロード選択と呼ぶ。暗黙的変換シーケンスの"優劣"は一つの実引数/仮引数型におけるオーバーロード解決の優先順位を定める。

二つの関数宣言は同スコープ、同名、同仮引数宣言の場合に同じ関数を参照する(16.2/p1)。以下の場合はオーバーロードできない(16.1/p2)。

  • 戻り値型あるいは/および例外仕様(exception sepcification)だけが異なる場合
  • スタティックメンバ関数と同じ仮引数宣言を持つ非スタティックな全てのメンバ関数
  • 同じ仮引数宣言を持つメンバ関数の少なくとも一つが参照修飾を持つ場合の参照修飾を持たないもの
  • 仮引数宣言がtypedef別名であることだけが異なる場合
  • 仮引数宣言がポインタ型(*)であるか配列型([])であるかのみ異なる場合(配列型宣言はポインタ型に修正されるため)
  • 仮引数宣言が関数型であるかその関数へのポインタ型であるかのみ異なる場合(関数型宣言は関数ポインタ型に修正されるため)
  • 仮引数宣言のトップレベルcv修飾のみが異なる場合
  • 仮引数宣言でデフォルト値のみが異なる場合

オーバーロード解決は七つの文脈で行われ(16.3/p2)、以下に列挙する。それぞれの末尾に本サイトが便宜上使用する文脈名を括弧で追記するが、規格に照らせば厳密ではない。

  • 関数コールの構文で名前付き関数をコールする(名前付き関数コール)。
  • 名前付きオブジェクトの関数コールオペレータ、関数ポインタへの変換関数、関数ポインタ参照への変換関数、関数参照への変換関数を、関数コールの構文でコールする(関数オブジェクトコール)。
  • 式の参照する演算子をコールする(演算子コール)。
  • クラスオブジェクトのデフォルト/直接初期化をコールする(コンストラクタコール)。文脈は初期化される型あるいは派生型からのコピー初期化も含む。
  • クラスオブジェクトのコピー初期化におけるユーザー定義変換をコールする(クラス型ユーザー定義変換)。
  • クラス型の式から非クラス型を初期化する変換関数をコールする(非クラス型ユーザー定義変換)。
  • 汎左辺値式あるいは純右辺値式へ参照を直接バインドする変換関数をコールする(直接バインドユーザー定義変換)。

オーバーロード解決は各文脈のルールで関数候補(candidate functions)を列挙する。共通ルールで関数候補から実行可能関数(viable functions)を絞り込み、そこから最優関数(best viable function)を選択する。最優関数が無いか複数であればオーバーロード解決は失敗し、最優関数がメンバ関数でアクセス不可なら不適格とする(16.3/p2-3)。関数候補/実行可能関数はメンバ関数あるいは非メンバ関数で(16.3.1/p2)、実引数リストをその仮引数リストと比較して実行可能あるいはオーバーロード解決の優劣を判断する。コンストラクタを除くメンバ関数の場合スタティックおよび非スタティックともに暗黙オブジェクト実引数(implied object argument)と暗黙オブジェクト仮引数(implicit object parameter)を比較対象に加え(p2)、それぞれのリストにおける最初の実引数/仮引数とする(p3)。クラスXの非スタティックなメンバ関数における暗黙オブジェクト仮引数型は以下とする(p4)。

  • メンバ関数が参照修飾子(ref-qualifier)を持たない、あるいは&を持つ場合、cv Xへの左辺値参照
  • メンバ関数が参照修飾子&&を持つ場合、cv Xへの右辺値参照

関数テンプレートが関数候補となる場合、候補となる(複数かもしれない)関数テンプレート特殊化がテンプレート実引数推定から生成される(p7)。=deleteされたデフォルトのムーブコンストラクタ/代入演算子は常に関数候補から除く(p8)。

関数候補

七つの文脈は関数コールとユーザー定義変換に大別され、演算子コールは組み込み演算子を含めて演算子関数の形式でオーバーロード解決するので関数コールと見なす。以下に各文脈の関数候補を説明するが、サンプルは最優関数の選択を含めてソースコードに示し、そこで詳細な考察を加える。

名前付き関数コール(16.3.1.1.1)を説明する。後置式(式リスト)の形式をとり(16.3.1.1/p1)、後置式は名前付き関数(named function)である。名前付き関数は後置式.id式(後置式はクラス型オブジェクト)、後置式->id式(後置式はクラス型オブジェクトポインタ)、一次式のいずれかで、前者二つは修飾付き関数コール(qualified funciton call)、後者一つは修飾なし関数コール(unqualified function call)と呼ぶ(16.3.1.1.1/p1)。修飾付き関数コールは後置式に与えるクラスオブジェクトを暗黙オブジェクト仮引数とするメンバ関数を候補とする(p2)。修飾なしの場合は一次式に自由関数(メンバでない関数)あるいはメンバ関数を与えて(8.2.2/p1)候補とし、後者の場合は暗黙オブジェクト仮引数に(*this)を与える(16.3.1.1.1/p3)。

覚え書き
関数コールの一般形式を規格の"postfix-expression(expression-list)"の日本語訳として"後置式(式リスト)"と表した(8.2/p1)。後置式(postfix-expression)は左から右へとグループされる式と定義するが、ここの後置式はあくまでも関数として機能するもの(自由関数、メンバ関数、関数オブジェクトなど)に限定する。また式リスト(expression-list)は実引数リスト(argument-list)に等しく、"(式リスト)"の括弧は補足説明でなく実引数リストを囲む丸括弧を表す。

関数オブジェクトコール(16.3.1.1.2)を説明する。クラスオブジェクトの()演算子コールや変換関数による関数ポインタ型などのコールの文脈でも後置式(式リスト)の形式をとり(16.3.1.1/p1)、後置式はクラス型オブジェクトである。後置式がcv T型のオブジェクトEに評価されるとすれば(E).operator()を候補とする(16.3.1.1.2/p1)。さらにT(あるいは基底型)が関数ポインタ型、関数への参照型、関数ポインタへの参照型への変換関数を持ち、変換関数のcv修飾がcv Tのそれより大きい場合、変換結果の関数を代理コール関数(surrogate call function)として候補に加える(p2)。

演算子コール(16.3.1.2)は後述する。

コンストラクタコール(16.3.1.3)を説明する。クラス(T)オブジェクトのデフォルト/直接初期化の文脈で、直接初期化、Tあるいは派生型からのコピー初期化、デフォルト初期化でコンストラクタを選択する(16.3.1.3/p1)。コピー初期化の文脈にない場合は全てのコンストラクタを候補とし、コピー初期化の場合は全ての変換コンストラクタ(15.3.1/p1)を候補とする。

クラス型ユーザー定義変換(16.3.1.4)を説明する。クラス(cv1 T)オブジェクトのコピー初期化におけるユーザー定義変換の文脈で、初期化式をユーザー定義変換する(16.3.1.4/p1)。

  • Tの変換コンストラクタを候補とする。
  • 初期化式がクラスcv S型であればSおよび基底型の非explicitな変換関数を考慮する。あるコンストラクタの第一仮引数がcv修飾されているかもしれないTへの参照で、そのコンストラクタをcv2 T型オブジェクト直接初期化の文脈で単一の実引数でコールして一時オブジェクトを第一仮引数にバインドする場合、explicitな変換関数も考慮する。これらの変換関数のうち、Sのスコープにあってcv修飾を除いた型がTあるいは派生型であれば候補とする。参照型を返す変換関数は参照方法に依存して左辺値あるいは期限切れ値式を返すとして、候補選択では参照を除いた型とする。

非クラス型ユーザー定義変換(16.3.1.5)を説明する。クラス型(cv S)の式から非クラス型(cv1 T)を初期化する変換関数の文脈で、初期化式に変換関数を適用する(16.3.1.5/p1)。

  • Sおよび基底型の変換関数を考慮する。SのスコープにあってTあるいはTへ標準変換シーケンスで変換可能な型への非explicitな変換関数を候補とする。直接初期化ならSのスコープにあってTあるいはTへ修飾変換可能な型へのexplictな変換関数も候補とする。候補選択にあたってcv修飾された型を返す変換関数はcv修飾されていない型とする。参照型を返す変換関数は参照方法に依存して左辺値式あるいは期限切れ値式を返すとして、候補選択では参照を除いた型とする。

直接バインドユーザー定義変換(16.3.1.6)を説明する。汎左辺値式あるいは純右辺値式へ参照を直接バインドする変換関数の文脈で、初期化式に変換関数を適用した結果にバインドする(16.3.1.6/p1)。cv1 Tへの参照をクラスcv S型の初期化式で初期化する。

  • Sおよび基底型の変換関数を考慮する。Sのスコープにあってcv2 T2への左辺値参照(左辺値参照あるいは関数への右辺値参照を初期化する場合)、あるいはcv2 T2かそれへの右辺値参照(右辺値参照あるいは関数への左辺値参照を初期化する場合)を返し、cv1 Tがcv2 T2への参照互換である非explicitな変換関数を候補とする。直接初期化ならSのスコープにあってcv2 T2への左辺値参照、cv2 T、cv2 Tへの右辺値参照を返し、T2がTと同じか修飾変換可能なexplicitな変換関数も候補とする。
覚え書き
汎左辺値式あるいは純右辺値式へ参照を直接バインドする変換関数の文脈で、"右辺値参照の初期化"とは"左辺値参照で初期化されなかったconst修飾の左辺値参照の初期化"を含むと思うが、確証が無い。
覚え書き
関数への参照について補足する。関数への参照は右辺値/左辺値にかかわらず常に左辺値式で区別できず(8/p7、8.2.9/p1)(William M. Miller, A Taxonomy of Expression Value Categories, 2010, JTC1/SC22/WG21 N3055, p.2)、左辺値参照は左辺値/右辺値参照を初期化できて、右辺値参照もまた左辺値/右辺値参照を初期化できる。

いずれの文脈でも非集約クラス型Tを{...}がリスト初期化する場合、オーバーロード解決は2ステップで行われる(16.3.1.7/p1)。{...}が{}でTがデフォルトコンストラクタを持つ場合は最初のステップは省略される。コピーリスト初期化がexplicitなコンストラクタ(すなわち変換コンストラクタでないコンストラクタ)を選択すれば不適格とする

  • 最初に関数候補はTの初期化リストコンストラクタとして、{...}を単一の実引数とする。
  • 実行可能な初期化リストコンストラクタが見つからなければ、Tの全てのコンストラクタを関数候補とする。{a}であれば実引数リストにaを与え、{}であれば実引数リスト空とする。

演算子コールの関数候補

演算子コールの文脈における関数候補を説明する(16.3.1.2)。

演算子の文脈で、被演算子のいずれもがクラス型あるいは列挙型でなければ演算子は組み込み演算子(8)である(16.3.1.2/p1)。いずれかがクラス型あるいは列挙型の場合、ユーザー定義の演算子関数が使用されるかもしれず、またはクラス型なら組み込み演算子に渡す型へユーザー定義変換するかもしれない(p2)。一部の組み込み演算子は列挙型を被演算子にできるが(8.8/p1、8.9/p1、8.11/p1、8.12/p1、8.13/p1)、ユーザー定義した演算子関数に置き変わるかもしれない(16.3.1.2/p3.3.4)。演算子関数は非スタティックなメンバ関数であるか、少なくとも一つの仮引数型がクラス、クラスへの参照、列挙型、列挙型への参照である非メンバ関数である(16.5/p6)。オーバーロード解決は演算子を等価の関数コールに置き換えるが、評価順は常に組み込み演算子のそれに従う。

メンバ関数コール 非メンバ関数コール 演算子(@) N4659
@a (a).operator@() operator@(a) * & + - ! ~ ++ -- 16.5.1
a@b (a).operator@(b) operator@(a,b) + - * / % ^ & | < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || , ->* 16.5.2
a=b (a).operator=(b) = 16.5.3
a[b] (a).operator[](b) [] 16.5.5
a-> (a).operator->() -> 16.5.6
a@ (a).operator@(0) ++ -- 16.5.7

以降、二項演算子を前提として被演算子aと被演算子bの存在を仮定するが、単項演算子の場合はbの記述を省くものとする。aのcv修飾を除いた型をT1、bのcv修飾を除いた型をT2として、関数候補はメンバ関数候補(member candidates)、非メンバ関数候補(non-member candidates)、組み込み演算子候補(built-in candidates)の結合とする(16.3.1.2/p3,p6)。

  • メンバ関数候補は、T1が完全クラス型/定義中クラス型の場合にT1operator@で修飾付き名前解決される全て。
  • 非メンバ関数候補は、operator@の修飾なし関数コールで名前解決されるものの中でメンバ関数を除く全て。ただしaもbもクラス型でない場合は、aがT1型かcv T1への参照型でT1が列挙型である、あるいはbがT2型かcv T2への参照型でT2が列挙型である非メンバ関数とする。
  • 組み込み演算子候補は、,、&の単項演算子、->については空とする。それ以外については後述する仮定演算子関数で、aとbで形式的に関数コール可能で、関数テンプレート特殊化を除く非メンバ関数候補に同じ仮引数型リストが無いものとする。

()はクラスオブジェクトのoperator()で既述した。.、.*、、?:はオーバーロードできない(16.5/p3)。new、new[]、delete、delete[]はライブラリ定義をユーザーが置き換えられるがオーバーロードではない(p5、6.7.4/p2)。+、-、*、&は単項演算子と二項演算子の両方でオーバーロードできる(16.5/p2)。a->bでオーバーロードされた->のbはoperator->の実引数とならず、operator->の戻り値に->を適応するbとなる(16.3.1.2/p8)。,、&の単項演算子、->で実行可能関数が無ければ組み込み演算子を選択する(16.3.1.2/p9)。

組み込み演算子もオーバーロード解決で関数コールに置換される(8/p3)ため、その演算子関数が定められる(16.6/p1)。この関数は実際にはコールできず、つまり宣言/定義されているわけではないので、組み込み演算子に代理する仮定の存在として仮定演算子関数と呼ぶことにする。aまたはbがクラス型あるいは列挙型の場合に仮定演算子関数が実行可能関数となるのは、aまたはbが仮定演算子関数の仮引数型に変換できる場合に限られる。仮定演算子関数は演算子と被演算子型の組み合わせで64種類におよび、詳細は規格を参照しろと言うほかない(p4-26)。オーバーロードできない?:にも仮定演算子関数が定められ(p25-26)、その場合は第三の被演算子cが存在する。

,、&の単項演算子、->に仮定演算子関数は定めないが、実行可能関数がなければそれぞれの組み込み演算子を選択することは既に述べた。これらが例外となるのは、仮定演算子関数は必ずスカラー型(数値型、列挙型、ポインタ型など)(6.9/p9)を仮引数型とするためと想像する。例外となる演算子はスカラー型以外の被演算子を受けて、その仮定演算子関数を定めるとユーザー定義の演算子関数と曖昧でオーバーロード解決できない。ところで?:もスカラー型以外を被演算子bあるいはcに受けることができるが、そもそも最初からオーバーロードできない。

64種類から2例のみ挙げる。第一例はbool以外の数値型Tについて、前置++の仮定演算子関数はvq T& operator++(vq T&)で(16.6/p4)、vqはvolatileあるいは空とする。第二例は列挙型あるいはポインタ型Tについて、<の仮定演算子関数はbool operator<(T,T)である(p16)。第一例は大部分の仮定演算子関数がそうであるように、仮引数リストを同じくしたユーザー定義の演算子関数を宣言/定義できない(16.5/p6)。第二例はその例外の一つで、列挙型は組み込み演算子<を持つ(8.9/p1)が特定の列挙型Eについてoperator<(E,E)をユーザー定義できて、その演算子関数はTをEとした仮定演算子関数に置き換わる(16.3.1.2/p3.3.4)。

=の仮定演算子関数は、例えば数値型Tについて、vq L& operator=(vq L&,R)である(16.6/p19)。aはvq L&型の仮引数を初期化するが、一時オブジェクトであってはならず、またユーザー定義変換は行われない(16.3.1.2/p4)。なお、クラス型の=は暗黙定義かどうかにかかわらず(a).operator=(b)なので、aは常に暗黙オブジェクト仮引数を初期化してユーザー定義変換はできない(16.3.3.1/p4.2)。

ユーザー定義の演算子関数は通常の関数と同じくスコープを持つが、仮定演算子関数はスコープを持たない。スコープは識別子(例えばoperator*)の属性であるから、例えば内側の名前空間にあるユーザー定義operator*(T)は外側の名前空間にあるユーザー定義operator*(T,T)を隠す(16.5.1/p2)。なおユーザー定義が仮定演算子関数に置き換わる場合は、識別子(例えばoperator<)と仮引数リストが共に一致する場合に限定される。

実行可能関数

実行可能関数は関数候補から、2ステップで絞り込む(16.3.2/p1)。

第一に、実引数がm個あるとして、実行可能関数はm個を受ける十分な仮引数を持たねばならない(p2)。

  • m個の仮引数を持つ関数候補の全てを実行可能(viable)とする
  • m個より少ない仮引数を持つ関数候補が実行可能であるのは、それが仮引数に省略記号(...)を持つ場合だけである
  • m個より多い仮引数を持つ関数候補が実行可能であるのは、m+1番目以降の仮引数がデフォルト値を持つ場合だけである

第二に、実行可能とした関数候補が真に実行可能関数であるには、それぞれの実引数を仮引数型に変換する暗黙的変換シーケンスが存在しなければならない(p3)。仮引数が参照型であれば、暗黙的変換シーケンスは参照へのバインドを含む。非constな左辺値参照は右辺値式にバインドできず、右辺値参照は左辺値式にバインドできず、これらは参照型を仮引数に持つ実行可能関数の可否に大きく影響する。

最優関数の選択

ICSi(F)をi番目の実引数を関数Fのi番目の仮引数型への暗黙的変換シーケンスと定義する(16.3.3/p1)。Fがメンバ関数なら最初の実引数/仮引数は暗黙オブジェクトなので、ICS1(F)は暗黙オブジェクト実引数から暗黙オブジェクト仮引数へ変換する。Fがスタティックメンバ関数であればICS1(F)とICS1(G)に優劣の差はない(オーバーロード解決にスタティックメンバ関数の暗黙オブジェクトは関与しない)。なおICSi(F)の優劣基準は既述している。それ以外で、暗黙的変換シーケンスのオーバーロード解決における優劣を以下に定義する。実行可能関数F1が他の実行可能関数F2より優れるのは、全ての実引数iでICSi(F1)がICSi(F2)より劣らず、

  • ある実引数jでICSj(F1)がICSj(F2)より優れる、さもなくば、
  • 文脈がユーザー定義変換による初期化で、F1の返値型から初期化される型への標準変換シーケンスがF2のそれより優れる、さもなくば、
  • 文脈が汎左辺値式あるいは純右辺値式へ参照を直接バインドする変換関数で、関数型にバインドする場合に、F1の返値型が初期化される参照と同じ種類(左辺値参照あるいは右辺値参照)であり、F2がそうでない(関数型の場合、左辺値参照は左辺値/右辺値参照を初期化できて、右辺値参照もまた左辺値/右辺値参照を初期化できて、優劣基準がないと曖昧となる)、さもなくば、
  • F1が関数テンプレート特殊化でなく、F2が関数テンプレート特殊化、さもなくば、
  • F1とF2が関数テンプレート特殊化で、F1の関数テンプレートがF2のそれより特殊化されている、さもなくば、
  • クラステンプレート実引数推定(後述)の文脈で、F1が推論補助で生成されたもので、F2がそうでない、さもなくば、
  • クラステンプレート実引数推定の文脈で、F1がコピー推定候補で、F2がそうでない、さもなくば、
  • F1が非テンプレートのコンストラクタで、F2がコンストラクタテンプレートから生成された。

他より優れるただ一つの実行可能関数があればそれを最優関数とし、さもなくば不適格とする(p2)。最優関数の二つ以上の宣言、あるいはusing宣言で参照する二つ以上の宣言が、実引数デフォルト値を定めて実行可能となる場合は不適格とする(p3)。

クラステンプレート実引数推定

クラステンプレート実引数推定(Class Template Argument Deduction, CTAD)はC++17で追加された。関数テンプレート特殊化のテンプレート実引数推定にならい、コンストラクタの実引数から構築するクラステンプレート特殊化のテンプレート実引数を推定する。C++14以前、クラステンプレート特殊化にはテンプレート実引数を明示的に与える必要があった。C++標準ライブラリはヘルパーとしてmake_pair関数テンプレートなどの生成関数を多く供給するが命名法に一貫性が無く(例えばpairクラステンプレートに対してmake_pari関数テンプレート、back_insert_iteratorクラステンプレートに対してback_inserter関数テンプレート)、そもそも一般的な解決法とは言えなかった。

関数および関数テンプレートのセットを生成する(N4659 16.3.1.8/p1)。

  • プライマリクラステンプレート(暗黙的特殊化するデフォルトテンプレート)が定義されていれば、各コンストラクタに対して以下に示す関数テンプレート。
    • テンプレート仮引数がクラステンプレートのテンプレート仮引数と(もしあれば)コンストラクタのテンプレート仮引数を連結したものである。
    • 関数の仮引数型がコンストラクタのそれである。
    • 返値型がクラステンプレート特殊化である。
  • プライマリクラステンプレートCが定義されていない、あるいはコンストラクタを宣言していなければ、仮定のC()コンストラクタから前項が生成する追加関数テンプレート。
  • 仮定のC(C)コンストラクタから前々項が生成する追加関数テンプレートで、コピー推定候補(copy deduction candidate)と呼ぶ。
  • 各推論補助(deduction-guide)(17.9/p1)に対して以下に示す関数または関数テンプレート。
    • (もしあれば)テンプレート仮引数と関数の仮引数型が推論補助のそれらである。
    • 返値型が推論補助のテンプレートID(simple-template-id)である。

このセットを仮定されたクラスのコンストラクタの関数候補として既述した初期化とオーバーロード解決を行うが、その初期化子はCTADが実行される文脈で与えられる(16.3.1.8/p2)。二つの関数候補のオーバーロード解決における優劣が等しい場合、推論補助の生成した関数候補はそうでないものより優れる(16.3.3/p1.8)。これら概念的なコンストラクタ(notional constructor)は、explicitなコンストラクタ/推論補助から生成されていればexplicitとする。概念的なコンストラクタはpublicメンバとする。

サンプルコード

summationクラステンプレートは加算演算子(+)を持つT型(クラステンプレート仮引数)、あるいはT型へ変換可能なクラスであるU型(メンバ関数テンプレート仮引数)の、T型総和を計算する。コンストラクタはstd::initializer_list、配列、STLコンテナ、イテレータ範囲でオーバーロードされる。

#include <iostream>
#include <vector>
#include <numeric>
#include <boost/core/demangle.hpp>
// 加算可能なT型(クラステンプレート仮引数)の総和を計算するクラステンプレート
// T型あるいはT型に暗黙変換できるU型(メンバ関数テンプレート仮引数)のstd::initializer_list、配列、STLコンテナ、イテレータ範囲で構築する
// 配列あるいはSTLコンテナを受けるコンストラクタは参照型仮引数なので、要素をT型あるいはU型とする二つのオーバーロードが必要
// ただしイテレータ範囲によるオーバロードと同様にidentity_or_nestedヘルパークラステンプレートを利用すればT型のオーバーロードは省略できる
template<typename T> class summation
{
private:
T sum_;
public:
summation(std::initializer_list<T> a):summation(a.begin(),a.end()) {}
template<size_t N> summation(const T(&a)[N]):summation(a,a+N) {}
template<typename U,size_t N> summation(const U(&a)[N]):summation(a,a+N) {}
template<template<typename> class C> summation(const C<T>& a):summation(a.begin(),a.end()) {}
template<typename U,template<typename> class C> summation(const C<U>& a):summation(a.begin(),a.end()) {}
template<typename Iter> summation(Iter first,Iter last):sum_{std::accumulate(first,last,T{})} {}
friend std::ostream& operator<<(std::ostream& lhs,const summation& rhs)
{return lhs<<boost::core::demangle(typeid(T).name())<<":"<<rhs.sum_;}
};
// U型のstd::initializer_list、配列、STLコンテナからの推論補助
template<typename U> summation(std::initializer_list<U>)->summation<typename U::type>;
template<typename U,size_t N> summation(const U(&)[N])->summation<typename U::type>;
template<typename U,template<typename> class C> summation(const C<U>&)->summation<typename U::type>;
// イテレータ範囲からの推論補助
// T型のイテレータであれば以下
// template <class Iter> summation(Iter,Iter)->summation<typename std::iterator_traits<Iter>::value_type>;
// U型のイテレータであればtypeメンバが必要
// template <class Iter> summation(Iter,Iter)->summation<typename std::iterator_traits<Iter>::value_type::type>;
// SFINAEによる補助クラステンプレートidentity_or_nestedでどちらかを選択する
// - https://cpppatterns.com/patterns/class-template-sfinae.html
// - https://stackoverflow.com/questions/9644477/how-to-check-whether-a-class-has-specified-nested-class-definition-or-typedef-in
template<typename,typename R=void> struct enable_if_type {using type=R;};
template<typename T,typename Enable=void> struct identity_or_nested {using type=T;};
template<typename U> struct identity_or_nested<U,typename enable_if_type<typename U::type>::type> {using type=typename U::type;};
template<class Iter> summation(Iter,Iter)
->summation<typename identity_or_nested<typename std::iterator_traits<Iter>::value_type>::type>;
// 特殊化がU型となるクラステンプレート、typeメンバはTを返す
template<typename T> class convertible
{
private:
T val_;
public:
using type=T;
convertible(T val):val_{val} {}
operator T() const {return val_;}
};
int main()
{
// T型のstd::initializer_list、配列、STLコンテナ、イテレータ範囲によるCTAD
std::cout<<summation{1,2}<<std::endl;
short a1[]={1,2,3};
std::cout<<summation(a1)<<std::endl;
auto v1=std::vector<double>{1,2,3,4};
std::cout<<summation(v1)<<std::endl;
std::cout<<summation(v1.begin(),v1.end())<<std::endl;
// U型のstd::initializer_list、配列、STLコンテナ、イテレータ範囲によるCTAD
std::cout<<summation{convertible{11},convertible{12}}<<std::endl;
convertible<short> a2[]={11,12,13};
std::cout<<summation(a2)<<std::endl;
auto v2=std::vector<convertible<double>>{11,12,13,14};
std::cout<<summation(v2)<<std::endl;
std::cout<<summation(v2.begin(),v2.end())<<std::endl;
return 0;
}

推論補助はU型を要素とするstd::initializer_list、配列、STLコンテナのそれぞれに必要とする。イテレータ範囲も推論補助を必要とするがT型イテレータとU型イテレータで処理が異なり、しかしコンストラクタのオーバーロードで対応できず、SFINAEを利用した一つの推論補助で対応させる。

サンプルコードで生成される概念的なコンストラクタをmake_summation関数として表せば以下となる。CTADはこれらからオーバーロード解決される概念的なコンストラクタ(make_summation)に対応するコンストラクタ(summation)を使用する。

// オーバーロード解決に供される概念的なコンストラクタ
// コンストラクタが生成
template<typename T>
summation<T> make_summation(std::initializer_list<T> a) {return summation<T>{a};} // (※1a)
template<typename T,size_t N>
summation<T> make_summation(const T(&a)[N]) {return summation<T>{a};} // (※2a)
template<typename T,typename U,size_t N>
summation<T> make_summation(const U(&a)[N]) {return summation<T>{a};}
template<typename T,template<typename> class C>
summation<T> make_summation(const C<T>&a) {return summation<T>{a};} // (※3a)
template<typename T,typename U,template<typename> class C>
summation<T> make_summation(const C<U>&a) {return summation<T>{a};}
template<typename T,typename Iter>
summation<T> make_summation(Iter first,Iter last) {return summation<T>{first,last};}
// コピー推定候補
template<typename T>
summation<T> make_summation(summation<T> rhs) {return summation<T>{rhs};}
// 推論補助が生成、他条件の優劣が等しいコンストラクタ生成のものより優れる
template<typename U>
summation<typename U::type>
make_summation(std::initializer_list<U> a) {return summation<typename U::type>{a};} // (※1b)
template<typename U,size_t N>
summation<typename U::type>
make_summation(const U(&a)[N]) {return summation<typename U::type>{a};} // (※2b)
template<typename U,template<typename> class C>
summation<typename U::type>
make_summation(const C<U>& a) {return summation<typename U::type>{a};} // (※3b)
template<typename Iter>
summation<typename identity_or_nested<typename std::iterator_traits<Iter>::value_type>::type>
make_summation(Iter first,Iter last)
{return summation<typename identity_or_nested<typename std::iterator_traits<Iter>::value_type>::type>{first,last};}

(※1a)~(※3a)のそれぞれが(※1b)~(※3b)のそれぞれとテンプレート仮引数と関数仮引数型が等しく、曖昧である事に注目したい。つまりmake_summationを実際にコーディングして例えばU型配列a2でmake_summation(a2)すれば曖昧エラーでコンパイルできない。make_summationはあくまでもCTADにおけるオーバーロード解決の説明として仮定された関数なので、実際のCTADがそうなるとは限らない。T型配列a1とU型配列a2でCTADで説明する。summation(a1)の概念的なコンストラクタは(※2a)と(※2b)のどちらであるが、T型はtypeメンバを持たないので(※2b)はSFINAEで排除されて(※2a)を選択する。summation(a2)ならU型はtypeメンバを持ち(※2b)は排除されず、(※2a)と(※2b)は通常なら曖昧であるが、推論補助の生成した関数をより優れるとして(※2b)を選択する。