|
本サイトは移転しました。旧アドレスからのリダイレクトは2025年03月31日(月)まで有効です。
|
🛈 | ✖ |
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)として変換カテゴリとする。派生から基底変換は標準変換の一つと見なさないが、標準変換シーケンスを構成するものとしてユーザー定義変換ではない。
ユーザー定義変換とは変換コンストラクタ(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/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)でユーザー定義変換した一時オブジェクトからのコピーが曖昧となる。
仮引数型が参照の場合は以下とする(16.3.3.1.4)。実引数に直接バインド(11.6.3)する場合、実引数の型が同じなら無変換(identity conversion)とする(16.3.3.1.4/p1)。直接バインドする場合で、仮引数型がクラスへの参照で実引数の型がその派生なら、派生から基底変換(derived-to-base conversion)で変換ランクとする。変換関数の結果に直接バインドする場合、ユーザー定義変換シーケンスとして第二の標準変換シーケンスは無変換あるいは基底型に変換する。直接バインドしない場合、実引数を参照される型に変換してバインドするが、これは参照される型の一時変数をコピー構築する事に相当する(p2)。標準変換シーケンスはconstでない左辺値参照を右辺値式にバインドできず、右辺値参照を左辺値式にバインドできない(p3)。
実引数に現れるリスト初期化{...}は式でなく特別のルール(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)。
暗黙的変換シーケンスの優劣は、関数オーバーロードが一つの実引数を仮引数型に変換する場合に可能な暗黙的変換シーケンスの複数から一つを選択する基準を定義する(16.3.3.2/p1)。つまり後述のICSi(F)の選択基準にあたる。
標準変換シーケンスはユーザー定義変換シーケンスより優れ、ユーザー定義変換シーケンスは省略記号変換シーケンスより優れる(p2)。
リスト初期化シーケンスL1とL2で、L1がL2より優れる条件は(p3.1)、
標準変換シーケンスS1とS2で、S1がS2より優れる条件は(p3.2)、
ユーザー定義変換シーケンスU1とU2で両者が同じ変換関数/変換コンストラクタを持つ、あるいは同じ集約クラスを初期化する場合、U1がU2よりより条件は(p3.3)、
標準変換シーケンスの優劣はランクが定め、優れる順に完全一致ランク、拡張ランク、変換ランクとする(p4)。同じランクにある二つの変換シーケンスは以下を除き優劣が定まらない。
オーバーロード関数(overloaded function)は複数の同名関数がそれぞれ異なる仮引数宣言を持つ。関数オーバーロード(function overloading)はオーバーロード関数のどれを選択するかを対応する実引数と仮引数型を比較して決定する(N4659 16/p1-2)。決定の過程をオーバーロード解決(overload resolution)と呼び、オーバーロード関数の一つを選択する事を本サイトはオーバーロード選択と呼ぶ。暗黙的変換シーケンスの"優劣"は一つの実引数/仮引数型におけるオーバーロード解決の優先順位を定める。
二つの関数宣言は同スコープ、同名、同仮引数宣言の場合に同じ関数を参照する(16.2/p1)。以下の場合はオーバーロードできない(16.1/p2)。
オーバーロード解決は七つの文脈で行われ(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)。
関数テンプレートが関数候補となる場合、候補となる(複数かもしれない)関数テンプレート特殊化がテンプレート実引数推定から生成される(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)。
関数オブジェクトコール(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)。
非クラス型ユーザー定義変換(16.3.1.5)を説明する。クラス型(cv S)の式から非クラス型(cv1 T)を初期化する変換関数の文脈で、初期化式に変換関数を適用する(16.3.1.5/p1)。
直接バインドユーザー定義変換(16.3.1.6)を説明する。汎左辺値式あるいは純右辺値式へ参照を直接バインドする変換関数の文脈で、初期化式に変換関数を適用した結果にバインドする(16.3.1.6/p1)。cv1 Tへの参照をクラスcv S型の初期化式で初期化する。
いずれの文脈でも非集約クラス型Tを{...}がリスト初期化する場合、オーバーロード解決は2ステップで行われる(16.3.1.7/p1)。{...}が{}でTがデフォルトコンストラクタを持つ場合は最初のステップは省略される。コピーリスト初期化がexplicitなコンストラクタ(すなわち変換コンストラクタでないコンストラクタ)を選択すれば不適格とする
演算子コールの文脈における関数候補を説明する(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)。
()はクラスオブジェクトの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)。
第二に、実行可能とした関数候補が真に実行可能関数であるには、それぞれの実引数を仮引数型に変換する暗黙的変換シーケンスが存在しなければならない(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)より劣らず、
他より優れるただ一つの実行可能関数があればそれを最優関数とし、さもなくば不適格とする(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)。
このセットを仮定されたクラスのコンストラクタの関数候補として既述した初期化とオーバーロード解決を行うが、その初期化子は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コンテナ、イテレータ範囲でオーバーロードされる。
推論補助はU型を要素とするstd::initializer_list、配列、STLコンテナのそれぞれに必要とする。イテレータ範囲も推論補助を必要とするがT型イテレータとU型イテレータで処理が異なり、しかしコンストラクタのオーバーロードで対応できず、SFINAEを利用した一つの推論補助で対応させる。
サンプルコードで生成される概念的なコンストラクタをmake_summation関数として表せば以下となる。CTADはこれらからオーバーロード解決される概念的なコンストラクタ(make_summation)に対応するコンストラクタ(summation)を使用する。
(※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)を選択する。