編集:この投稿を無視してください-Doug Gregorによって実装された半順序のclangsアルゴリズムを研究した後(この記事の執筆時点では部分的にしか実装されていませんが-OPの質問に関連するロジックは十分に実装されているようです)-それ推論されていないコンテキストを単なる別のテンプレートパラメータとして扱うように見えます。これは、明示的なvoid *引数を使用したオーバーロードは、より特殊なバージョンであり、あいまいさがないことを示しています。いつものようにコモーは正しいです。さて、この振る舞いを明確に定義する規格の文言については、それは別の問題です...
この投稿はcomp.lang.c++。moderatedにも投稿されており、そこでも混乱を引き起こしているようです-議論は明らかにここで尋ねられた質問に関連しているので、私はそのグループへの私の答えもここに投稿すると思いました- 。
On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> wrote:
You are going one step too fast here. How do you know (and would the compiler know) that there is no specialisation of Const<Q> such that Const<Q>::type != void?
As far as I can see, the compiler would transform the parameter-list of A to: AT=(Q, <unknown>*). To call B with these parameters requires an implicit conversion (<unknown>* to void*) and therefore A is less specialised than B.
これは間違っていると思います。どの関数がより特殊化されているかを確認するとき(半順序付け中)、コンパイラはパラメータリストを次のように変換します(Q, void*)
-つまり、実際に関連するテンプレートをインスタンス化し(最適な一致)、その中を「type」の値を探します-これで場合によっては、プライマリテンプレートに基づいて、無効になります*。
部分的な特殊化に関するあなたのポイントに関して-どのテンプレートが他のテンプレートよりも特殊化されているかをチェックするとき、使用できる唯一のタイプは一意に生成されたタイプです-宣言のインスタンス化の時点で他の特殊化がある場合(過負荷解決が行われている)それらは考慮されます。後でそれらを追加し、それらが選択される必要がある場合は、ODRに違反することになります(14.7.4.1による)
部分的/明示的な特殊化も候補セットの形成中に考慮されますが、今回は関数への実際の引数のタイプを使用します。(Xの)最も一致する部分特殊化の結果、一部のパラメーターに対してより適切な暗黙の変換シーケンスを持つ関数型が得られた場合、半順序フェーズに到達することはなく、その「より適切な」関数が(作成する前に)選択されます。半順序フェーズに移行します)
これは、さまざまなステップで何が行われるべきかについてのコメント付きの例です。
template<class T, bool=true> struct X; // Primary
template<class T> struct X<T,true> { typedef T type; }; // A
template<> struct X<int*,true> { typedef void* type; }; // B
template<class T> void f(T,typename X<T>::type); //1
template<class T> void f(T*,void*); //2
int main()
{
void* pv;
int* pi;
f(pi,pi);
// two candidate functions: f1<int*>(int*,void*), f2<int>(int*,void*)
// Note: specialization 'B' used to arrive at void* in f1
// neither has a better ICS than the other, so lets partially order
// transformed f1 is f1<U1>(U1,X<U1,true>::type) --> f1<U1>(U1,U1)
// (template 'A' used to get the second U1)
// obviously deduction will fail (U1,U1) -> (T*,void*)
// and also fails the other way (U2*, void*) -> (T,X<T>::type)
// can not partially order them - so ambiguity
f(pv,pv);
// two candidate functions: f1<void*>(void*,void*), f2<void>(void*,void*)
// Note: specialization 'A' used to arrive at second void* in f1
// neither has a better ICS than the other, so lets partially order
// transformed f1 is f1<U1>(U1,X<U1>::type) --> f1<U1>(U1,U1)
// (template 'A' used to get the second U1)
// obviously deduction will fail (U1,U1) -> (T*,void*)
// and also fails the other way (U2*, void*) -> (T,X<T>::type)
// can not partially order them - so ambiguity again
}
また、プライマリテンプレートに定義がない場合、SFINAEは半順序フェーズで動作し、どちらも他方から推測できず、あいまいさが生じるはずです。
また、これらの関数のいずれかのインスタンス化のポイントが翻訳ユニットの他の場所に移動された場合に別の一致につながる別のテンプレートを追加すると、明らかにODRに違反します。
On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> wrote:
まず、より専門的であるということは、過負荷解決によってそのテンプレートを選択できるタイプが少ないことを意味します。これを使用して、半順序の規則を次のように要約できます。Aは呼び出せるが、Bは呼び出せない、または過負荷解決がAを呼び出すことを好むようなAの型を見つけようとします。その型が見つかった場合、Bはより特殊化されます。 Aより。
ここでは議論はありません。しかし、現在のルールに基づくと、OPの例はあいまいである必要があります。
最後に、litbによって提起された2つの特定の質問に対する明確で明確な回答を次に示します。
1)これで、最初のパラメーターとして推定されたTの値が使用されますか?
はい-もちろん、それはテンプレート引数の推論を行っている必要があります-「リンク」を維持する必要があります。
2)さて、なぜ実装は2番目が代わりにより専門化されていると言うのですか?
彼らは間違っているので;)
これで問題が解決することを願っています-まだ不明な点があれば教えてください:)
編集:litbは彼のコメントで良い点を挙げました-おそらく、プライマリテンプレートは常に一意の生成されたタイプでインスタンス化に使用されると述べているのは強すぎるステートメントです。
プライマリテンプレートが呼び出されない場合があります。
私が得ているのは、半順序が発生しているときに、いくつかの一意の生成されたタイプが最適な専門分野に一致するために使用されるということです。そうです、それはプライマリテンプレートである必要はありません。私はそうするために上記の言語を編集しました。彼はまた、インスタンス化の時点の後で、より一致するテンプレートを定義することに関する問題を提起しました。インスタンス化のポイントに関するセクションによると、これはODRの違反になります。
標準では、A / Pペアが作成されると(temp.func.orderで説明されている変換ルールを使用して)、テンプレート引数の推論(temp.deduct)を使用して相互に推定され、そのセクションで次のケースが処理されます。推論されていないコンテキスト、テンプレートとそのネストされたタイプのインスタンス化、インスタンス化のポイントのトリガー。temp.pointセクションはODR違反を処理します(半順序の意味は、変換ユニット内のインスタンス化のポイントに関係なく変更されるべきではありません)。混乱がどこから来ているのかまだわかりませんか?–ファイサルバリ1時間前[このコメントを削除]
litb:"引数を作成するためにQをConst:: typeに入れるステップは、SFINAEルールによって明示的にカバーされていないことに注意してください。SFINAEルールは引数の演繹で機能します。Qを関数テンプレート関数パラメーターリストに入れる段落は次のとおりです。 14.5.5.2。 '
ここではSFINAEルールを使用する必要があります-どうして使用できないのでしょうか?私はそれが十分に暗示されていると感じています-私はそれがより明確になる可能性があることを否定しません、そして私は委員会にこれを明確にすることを勧めます-私はあなたの例を十分に解釈するために明確にする必要はないと思います。
それらをリンクする1つの方法を提供しましょう。(14.8.2)から:「明示的なテンプレート引数リストが指定されている場合、テンプレート引数はテンプレートパラメータリストと互換性があり、以下に説明する有効な関数型になる必要があります。そうでない場合、型の推定は失敗します。」
From(14.5.5.2/3) "使用される変換は次のとおりです。—タイプテンプレートパラメーターごとに、一意のタイプを合成し、関数パラメーターリスト内のそのパラメーターの出現ごとに、またはテンプレート変換関数の代わりに、戻り値を返します。タイプ。"
私の考えでは、上記の引用は、テンプレートパラメータごとに一意の生成型を「作成」したら、関数テンプレートに一意の型をテンプレート引数として明示的に指定することにより、関数宣言を暗黙的にインスタンス化する必要があることを意味します。これにより無効な関数型が生成される場合、変換だけでなく、さらに重要なことに、関数を半順序にするために必要な後続のテンプレート引数の推定が失敗します。
From(14.5.5.2/4) "変換された関数パラメーターリストを使用して、他の関数テンプレートに対して引数の推論を実行します。変換されたテンプレートは、推論が成功し、推定されたパラメータータイプが成功した場合にのみ、少なくとも他のテンプレートと同じように特殊化されます。完全に一致します(したがって、控除は暗黙の変換に依存しません)。」
変換された関数パラメーターリストが置換の失敗につながる場合、推論は成功しなかった可能性があります。そして、控除が成功しなかったので、それは他のものほど専門的ではありません-それは私たちが2つを半順序で進めるために知る必要があるすべてです。
litb:この場合に何が起こるかもわかりません:template<typename T> struct A;
template<typename T> void f(T, typename A<T>::type); template<typename T> void f(T*, typename A<T>::type);
確かに、それは有効なコードであることが意図されていますが、A :: typeを実行すると、テンプレート定義コンテキストでAがまだ定義されていないため、失敗します。順序を決定しようとしているときに、この種の置換から生じるテンプレートのインスタンス化に対して定義されたPOIはありません(半順序はコンテキストに依存しません。これは、関連する2つの関数テンプレートの静的プロパティです)。これは、修正が必要な標準。
わかりました-私は私たちが物事を異なって見ているところを見ていると思います。私があなたを正しく理解しているなら、あなたはこれらの関数テンプレートが宣言されると、コンパイラはそれらの間で選択するためにトリガーされる過負荷解決に関係なく、それらの間の半順序を追跡していると言っています。それがあなたがそれを解釈する方法であるならば、私はあなたがあなたが説明する上記の振る舞いを期待する理由を見ることができます。しかし、私は、規格がこれを要求または義務付けているとは思いません。
現在、標準では、半順序が関数の呼び出しに使用される型に依存しないことが明確になっています(これは、静的プロパティとして記述し、コンテキストに依存しない場合に参照しているものだと思います)。
また、この標準は、ICSに基づいてより適切な関数を選択できなかった場合、または1つである場合にのみ、過負荷解決(13.3.3 / 1)のプロセス中に関数テンプレート間の半順序(半順序を呼び出す)のみを考慮していることも明確です。はテンプレートであり、もう一方はそうではありません。[クラステンプレートの半順序化は別の問題であり、私の考えでは、その特定のクラスのインスタンス化を必要とする関連するコンテキスト(他のテンプレート定義)を使用します。]
したがって、私の意見では、関数テンプレートの半順序の機構は、オーバーロード解決が実行されるときに呼び出されるため、オーバーロード解決が実行されている時点で利用可能なコンテキストの関連部分(テンプレート定義と特殊化)を使用する必要があります。
したがって、私の解釈に基づいて、上記の「template struct A」を使用した例によれば、コードは有効です。半順序は、定義コンテキストでは実行されません。しかし、f((int *)0,0)の呼び出しを記述して、2つの関数間でオーバーロード解決を呼び出した場合、およびその時点で、コンパイラが候補宣言をアセンブルしようとするか、それらを半順序化しようとします(if関数型の一部として無効な式または型が生成された場合、SFINAEは私たちを助け、テンプレートの推定が失敗することを通知します(半順序に関する限り、これ以上のことはできないことを意味します)。テンプレートを変換することさえできなかった場合は、他のものよりも専門的です)。
POIに関しては、私がそうであるように、変換された関数型は、明示的に指定されたテンプレート引数リストを使用して(一意に生成された型を使用して)暗黙のインスタンス化を表すことになっていると確信している場合は、次の標準引用符が適切です。
14.6.4.1 / 1関数テンプレートの特殊化、メンバー関数テンプレートの特殊化、またはクラステンプレートのメンバー関数または静的データメンバーの特殊化の場合、特殊化が別のテンプレートの特殊化および参照元のコンテキストはテンプレートパラメータによって異なります。スペシャライゼーションのインスタンス化のポイントは、それを囲むスペシャライゼーションのインスタンス化のポイントです。
私がこれを解釈する方法は、変換された関数型と元の関数型のPOIは、実際の関数呼び出しによって作成された関数のPOIと同じであるということです。
litb:半順序はむしろ
a property of the syntactic form of parameters (i.e "T*" against "T(*)[N]"),
私だけなので、仕様の修正に投票します(「Qが、型に名前を付ける修飾IDのネストされた名前指定子に表示される場合、名前が付けられた型は「Q」です)。は別のユニークなタイプです
This means that in template<typename T> void f(T, typename Const<T>::type*);
the argument list is (Q, R*), for example.
Same for template<typename T> void f(T*, typename ConstI<sizeof(T)>::type);
the arg lisst would be (Q*, R). A similar rule would be needed for non-type parameters, of course.
が、これが自然な順序になるかどうかを確認するために、それについて考え、いくつかのテストケースを作成する必要があります。
ああ-今、あなたは私たち全員が直感的に期待するものを支持して曖昧さを解決する可能な解決策を提案しています-これは別の問題です、そして私はあなたのようにあなたが向かっている方向が好きですが、私もいくつか考えなければなりませんその作業性を宣言する前にそれに。
議論を続けてくれてありがとう。SOがコメントの配置だけに制限されていなかったらいいのにと思います。
私の投稿は編集できますので、よろしければ投稿内でお気軽にご返信ください。