9

[temp.class.order] §14.5.5.2 によるとt、この例での部分的な特殊化の選択:

template< typename >
struct s { typedef void v, w; };

template< typename, typename = void >
struct t {};

template< typename c >
struct t< c, typename c::v > {};

template< typename c >
struct t< s< c >, typename s< c >::w > {};

t< s< int > > q;

fこの例では、オーバーロードの選択と同等です。

template< typename >
struct s { typedef void v, w; };

template< typename, typename = void >
struct t {};

template< typename c >
constexpr int f( t< c, typename c::v > ) { return 1; }

template< typename c >
constexpr int f( t< s< c >, typename s< c >::w > ) { return 2; }

static_assert ( f( t< s< int > >() ) == 2, "" );

ただし、GCC、Clang、および ICC はすべて、最初の例があいまいであるとして拒否しますが、2 番目の例は受け入れます。

さらに奇妙なことに、最初の例は、::vが に置き換えられた場合、::wまたはその逆に置き換えられた場合に機能します。推論されていないコンテキストc::s< c >::は明らかに特殊化の順序で考慮されていますが、これは意味がありません。

標準に何か欠けているのでしょうか、それともこれらすべての実装に同じバグがありますか?

4

3 に答える 3

8

しばらくの間、非常にペダンティックなモードに切り替えます。はい、標準に何かが欠けていると思います。いいえ、この場合は何の違いもありません。

すべての標準的な参照は、現在のワーキング ドラフトである 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_CallTPOC_Conversion、およびTPOC_Otherです。自明です。この関数は、2 つのテンプレート間で 2 回呼び出しisAtLeastAsSpecializedAs、結果を比較します。

isAtLeastAsSpecializedAsフラグの値をTPOCオンにし、それに基づいていくつかの調整を行い、最終的に直接的または間接的に を呼び出しDeduceTemplateArgumentsByTypeMatchます。それが を返した場合Sema::TDK_SuccessisAtLeastAsSpecializedAs半順序付けに使用されるすべてのテンプレート パラメータに値があることを確認するために、もう 1 回だけチェックを行います。それも良ければ を返しますtrue

これが関数テンプレートの部分的な順序付けです。前のセクションで引用したパラグラフに基づいて、Sema::getMoreSpecializedTemplate適切に構築された関数テンプレートと のフラグを使用してクラス テンプレートの特殊化を呼び出すための部分的な順序付けTPOC_Other、およびすべてがそこから自然に流れることを期待していました。その場合、あなたの例はうまくいくはずです。驚き: そうではありません。

クラス テンプレートの特殊化の部分的な順序付けは から始まりSema::getMoreSpecializedPartialSpecializationます。最適化 (危険信号!) として、関数テンプレートを合成するのではなく、クラス テンプレートの特殊化自体をおよびDeduceTemplateArgumentsByTypeMatchの型として直接型推定するために使用します。これで問題ありません。結局のところ、それが関数テンプレートのアルゴリズムが最終的に行うことになることです。PA

ただし、演​​繹中にすべてがうまくいった場合は、(クラス テンプレートの特殊化のオーバーロード) を呼び出します。これは、特殊化への置換された引数が元の引数と同等でFinishTemplateArgumentDeductionあることのチェックを含む、置換およびその他のチェックを行います。部分的な特殊化が一連の引数と一致するかどうかをコードがチェックしていた場合、これは問題ありませんが、部分的な順序付け中はうまくいかず、私が知る限り、あなたの例で問題が発生します。

したがって、何が起こるかについてのリチャード・コーデンの仮定は正しいようですが、これが意図的であったかどうかは完全にはわかりません. これは私には見落としのように見えます。どのようにしてすべてのコンパイラが同じように動作するようになったのかは謎のままです。

私の意見では、from への 2 つの呼び出しを削除しFinishTemplateArgumentDeductionSema::getMoreSpecializedPartialSpecializationも害はなく、半順序付けアルゴリズムの一貫性が回復します。isAtLeastAsSpecializedAsすべてのテンプレート パラメーターが特殊化の引数から推定できることがわかっているため、すべてのテンプレート パラメーターに値があることを追加で確認する ( によって実行される) 必要はありません。そうでない場合、部分的な特殊化は一致に失敗するため、そもそも部分的な順序付けに到達しません。(そもそもそのような部分的な特殊化が許可されているかどうかはissue 549の主題です。Clang はそのような場合に警告を発行し、MSVC と GCC はエラーを発行します。とにかく、問題ではありません。)

補足として、これはすべて、変数テンプレートの特殊化のオーバーロードにも当てはまると思います。

残念ながら、私は Clang 用にセットアップされたビルド環境を持っていないため、現時点ではこの変更をテストできません。

于 2015-07-30T22:28:13.810 に答える
2

意図は例をコンパイルすることだと思いますが、標準は、半順序付け (14.5.5.1/1) で使用される合成された引数リストのテンプレート引数リストのマッチング時に何が起こるべきか (もしあれば) を明確に述べていません:

これは、クラス テンプレートの特殊化のテンプレート引数を、部分的な特殊化のテンプレート引数リストと一致させることによって行われます。

上記の段落は、以下で #1 が選択されるようにするために必要です。

template <typename T, typename Q> struct A;
template <typename T>             struct A<T, void> {}; #1
template <typename T>             struct A<T, char> {}; #2

void foo ()
{
  A<int, void> a;
}

ここ:

  1. テンプレート パラメータTint(14.5.5.1/2)と推定されます。
  2. 結果の引数リストは一致します: int== int, void== void(14.5.5.1/1)

半順序の場合:

template< typename c > struct t< c, typename c::v > {};  #3
template< typename c > struct t< s< c >, typename s< c >::w > {}; #4

最初のパラメーターの場合、#4 はより特殊化されており、2 番目のパラメーターは両方とも推定されないコンテキストです。型推論は #4 から #3 まで成功しますが、#3 から #4 までは成功しません。

コンパイラは、合成された引数リストに 14.5.5.1/1 の「引数リストが一致する必要がある」ルールを適用していると思います。これは、最初に合成された型Q1::vと 2 番目の型を比較しs<Q2>::wますが、これらの型は同じではありません。

これは、コンパイラがこれらの型が同じであると判断したため、 に変更vすると一部の例が機能する理由を説明している可能性があります。w

c::vこれは、部分的な順序付け以外の問題ではありません。これは、型がインスタンス化されるvoidなどのように型が具体的であるためです。

委員会が型等価性 (14.4) を適用することを意図している可能性もありますが、私はそうは思いません。標準はおそらく、半順序付けステップの一部として作成された合成型の一致 (または不一致) に関して何が起こるべきかを正確に明確にする必要があります。

于 2015-07-23T18:09:14.087 に答える