しばらくの間、非常にペダンティックなモードに切り替えます。はい、標準に何かが欠けていると思います。いいえ、この場合は何の違いもありません。
すべての標準的な参照は、現在のワーキング ドラフトである N4527 に対するものです。
[14.5.5.2p1] 言います:
2 つのクラス テンプレートの部分的な特殊化では、関数テンプレートの順序規則 (14.5.6.2) に従って、最初の関数テンプレートが 2 番目の関数テンプレートよりも特殊化されている場合、最初の関数テンプレートは 2 番目の関数テンプレートよりも特殊化されています。
- 最初の関数テンプレートは、最初の部分的な特殊化と同じテンプレート パラメーターを持ち、型が最初の部分的な特殊化のテンプレート引数を持つクラス テンプレートの特殊化である単一の関数パラメーターを持ち、
- 2 番目の関数テンプレートは、2 番目の部分特殊化と同じテンプレート パラメーターを持ち、型が 2 番目の部分特殊化のテンプレート引数を持つクラス テンプレート特殊化である単一の関数パラメーターを持ちます。
[14.5.6.2p1] に移動:
[...]オーバーロードされた関数テンプレート宣言の部分的な順序付けは、次のコンテキストで使用され、関数テンプレートの特殊化が参照する関数テンプレートを選択します。
- 関数テンプレートの特殊化 (13.3.3) への呼び出しのオーバーロード解決中。
- 関数テンプレートの特殊化のアドレスが取得されたとき。
- 関数テンプレートの特殊化である配置演算子 delete が配置演算子 new に一致するように選択された場合 (3.7.4.2、5.3.4)。
- フレンド関数宣言 (14.5.4)、明示的なインスタンス化 (14.7.2)、または明示的な特殊化 (14.7.3) が関数テンプレートの特殊化を参照する場合。
クラス テンプレートの特殊化の部分的な順序については言及されていません。ただし、 [14.8.2.4p3] は次のように述べています。
順序付けを決定するために使用される型は、部分的な順序付けが行われるコンテキストによって異なります。
- 関数呼び出しのコンテキストでは、使用される型は、関数呼び出しが引数を持つ関数パラメーターの型です。
- 変換関数の呼び出しのコンテキストでは、変換関数テンプレートの戻り値の型が使用されます。
- 他のコンテキスト (14.5.6.2) では、関数テンプレートの関数型が使用されます。
[14.5.6.2] を参照していますが、「その他のコンテキスト」と記載されています。[14.5.5.2] の規則に従って生成された関数テンプレートに半順序付けアルゴリズムを適用する場合、関数の場合と同様に、パラメーター型のリストではなく、関数テンプレートの関数型が使用されると結論付けることができます。電話。
したがって、最初のスニペットでの部分的な特殊化の選択はt
、関数呼び出しを含むケースではなく、(たとえば) 関数テンプレートのアドレスを取得するケースと同等であり、これも「その他のコンテキスト」に分類されます。
#include <iostream>
template<typename> struct s { typedef void v, w; };
template<typename, typename = void> struct t { };
template<typename C> void f(t<C, typename C::v>) { std::cout << "t<C, C::v>\n"; }
template<typename C> void f(t<s<C>, typename s<C>::w>) { std::cout << "t<s<C>, s<C>::w>\n"; }
int main()
{
using pft = void (*)(t<s<int>>);
pft p = f;
p(t<s<int>>());
}
(私たちはまだ非常にペダンティックなモードにいるので、[14.5.5.2p2] の例とまったく同じように関数テンプレートを書き直しました。)
言うまでもなく、これもコンパイルして出力しt<s<C>, s<C>::w>
ます。別の動作を生成する可能性はわずかでしたが、試してみる必要がありました. アルゴリズムがどのように機能するかを考えると、たとえば関数のパラメーターが参照型である場合 (関数呼び出しの場合は [14.8.2.4] の特別な規則がトリガーされますが、それ以外の場合はトリガーされません)、違いが生じたはずです。しかし、そのような形式は、クラス テンプレートの特殊化から生成された関数テンプレートでは発生しません。
ですから、この回り道は少しも役に立ちませんでしたが...language-lawyer
問題は、ここにいくつかの標準的な引用符を含める必要があったことです...
あなたの例に関連するアクティブなコアの問題がいくつかあります。
1157には、関連すると思われるメモが含まれています。
テンプレート実引数推定は、 aP
と a deduced
を一致させようとする試みA
です。P
ただし、と 推定されたA
が互換性がない場合、テンプレート引数推定は失敗するように指定されていません。これは、推定されていないコンテキストが存在する場合に発生する可能性があります。14.8.2.4 [temp.deduct.partial] パラグラフ 9 の括弧内のステートメントにもかかわらず、テンプレート実引数推定A
は、対応するP
.
それがそれほど明確に指定されているかどうかは完全にはわかりません。結局、 [14.8.2.5p1] は言う
[...]テンプレート引数の値を見つける[...]推定値の置換後[...]PをAと互換性のあるものにする.
[14.8.2.4] は [14.8.2.5] 全体を参照しています。ただし、関数テンプレートの部分的な順序付けは、推定されないコンテキストが関係する場合に互換性を求めないことは明らかであり、それを変更すると多くの有効なケースが壊れる可能性があるため、これは標準での適切な仕様の欠如にすぎないと思います.
程度は低いですが、1847は、テンプレートの特殊化への引数に現れる推定されないコンテキストと関係があります。解像度は1391を参照しています。その言い回しにはいくつかの問題があると思います-詳細はこの回答にあります。
私には、これらすべてがあなたの例がうまくいくはずだと言っています。
あなたと同じように、3 つの異なるコンパイラに同じ矛盾が存在するという事実に、私は非常に興味をそそられました。MSVC 14 が他のものとまったく同じ動作を示すことを確認した後、さらに興味をそそられました。そこで、時間ができたときに、Clang の機能を簡単に見てみようと思いました。それは決して迅速ではないことが判明しましたが、いくつかの答えが得られました。
このケースに関連するすべてのコードは にありlib/Sema/SemaTemplateDeduction.cpp
ます。
演繹アルゴリズムの中核はDeduceTemplateArgumentsByTypeMatch
関数です。演繹のすべてのバリアントはそれを呼び出すことになり、再帰的に使用されて、複合型の構造をウォークスルーします。時には、重くオーバーロードDeduceTemplateArguments
された関数のセットと、特定のタイプの演繹に基づいてアルゴリズムを調整するためのフラグの助けを借ります。完了し、型のフォームの部分が見られます。
この関数に関して注意すべき重要な点は、置換ではなく演繹を厳密に処理することです。型フォームを比較し、推定されたコンテキストに現れるテンプレート パラメーターのテンプレート引数値を推定し、推定されていないコンテキストをスキップします。他に行う唯一のチェックは、テンプレート パラメーターの推定引数値が一貫していることを確認することです。上記の回答で、半順序付け中に Clang が演繹を行う方法について、もう少し詳しく説明しました。
関数テンプレートの部分的な順序付けの場合、アルゴリズムはSema::getMoreSpecializedTemplate
メンバー関数で開始されます。メンバー関数は、型のフラグを使用して、enum TPOC
部分的な順序付けが行われているコンテキストを決定します。列挙子はTPOC_Call
、TPOC_Conversion
、およびTPOC_Other
です。自明です。この関数は、2 つのテンプレート間で 2 回呼び出しisAtLeastAsSpecializedAs
、結果を比較します。
isAtLeastAsSpecializedAs
フラグの値をTPOC
オンにし、それに基づいていくつかの調整を行い、最終的に直接的または間接的に を呼び出しDeduceTemplateArgumentsByTypeMatch
ます。それが を返した場合Sema::TDK_Success
、isAtLeastAsSpecializedAs
半順序付けに使用されるすべてのテンプレート パラメータに値があることを確認するために、もう 1 回だけチェックを行います。それも良ければ を返しますtrue
。
これが関数テンプレートの部分的な順序付けです。前のセクションで引用したパラグラフに基づいて、Sema::getMoreSpecializedTemplate
適切に構築された関数テンプレートと のフラグを使用してクラス テンプレートの特殊化を呼び出すための部分的な順序付けTPOC_Other
、およびすべてがそこから自然に流れることを期待していました。その場合、あなたの例はうまくいくはずです。驚き: そうではありません。
クラス テンプレートの特殊化の部分的な順序付けは から始まりSema::getMoreSpecializedPartialSpecialization
ます。最適化 (危険信号!) として、関数テンプレートを合成するのではなく、クラス テンプレートの特殊化自体をおよびDeduceTemplateArgumentsByTypeMatch
の型として直接型推定するために使用します。これで問題ありません。結局のところ、それが関数テンプレートのアルゴリズムが最終的に行うことになることです。P
A
ただし、演繹中にすべてがうまくいった場合は、(クラス テンプレートの特殊化のオーバーロード) を呼び出します。これは、特殊化への置換された引数が元の引数と同等でFinishTemplateArgumentDeduction
あることのチェックを含む、置換およびその他のチェックを行います。部分的な特殊化が一連の引数と一致するかどうかをコードがチェックしていた場合、これは問題ありませんが、部分的な順序付け中はうまくいかず、私が知る限り、あなたの例で問題が発生します。
したがって、何が起こるかについてのリチャード・コーデンの仮定は正しいようですが、これが意図的であったかどうかは完全にはわかりません. これは私には見落としのように見えます。どのようにしてすべてのコンパイラが同じように動作するようになったのかは謎のままです。
私の意見では、from への 2 つの呼び出しを削除しFinishTemplateArgumentDeduction
てSema::getMoreSpecializedPartialSpecialization
も害はなく、半順序付けアルゴリズムの一貫性が回復します。isAtLeastAsSpecializedAs
すべてのテンプレート パラメーターが特殊化の引数から推定できることがわかっているため、すべてのテンプレート パラメーターに値があることを追加で確認する ( によって実行される) 必要はありません。そうでない場合、部分的な特殊化は一致に失敗するため、そもそも部分的な順序付けに到達しません。(そもそもそのような部分的な特殊化が許可されているかどうかはissue 549の主題です。Clang はそのような場合に警告を発行し、MSVC と GCC はエラーを発行します。とにかく、問題ではありません。)
補足として、これはすべて、変数テンプレートの特殊化のオーバーロードにも当てはまると思います。
残念ながら、私は Clang 用にセットアップされたビルド環境を持っていないため、現時点ではこの変更をテストできません。