web-dev-qa-db-ja.com

クラステンプレートの特殊化の半順序と関数の合成

どのクラステンプレートの特殊化が優先されるかを選択するためのルールには、特殊化を関数テンプレートに書き直し、関数テンプレートの順序付けルール[temp.class.order]を介してどの関数テンプレートがより特殊化されるかを決定することが含まれます。この例を考えてみましょう。

#include <iostream>

template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;

template <class T, class U> struct A { };

template <class T> int foo(A<T, void_t<T>> ) { return 1; }
template <class T> int foo(A<T*, void> )     { return 2; }

int main() {
    std::cout << foo(A<int*, void>{});
}

Gccとclangの両方がここに2を出力します。これは以前のいくつかの例で理にかなっています-推論されていないコンテキスト(voidからvoid_t<T>)に対する推論は無視されるため、<T, void_t<T>>から<X*, void>への推論は成功しますが、<T*, void>から<Y, void_t<Y>>に対する推論は両方の引数で失敗します。結構です。

ここで、この一般化について考えてみましょう。

#include <iostream>

template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;

template <int I> struct int_ { static constexpr int value = I; };

template <class T, class U> struct A      : int_<0> { };
template <class T> struct A<T, void_t<T>> : int_<1> { };
template <class T> struct A<T*, void>     : int_<2> { };

int main() {
    std::cout << A<int*, void>::value << '\n';
}

Clangとgccはどちらも、この特殊化を12の間であいまいであると報告しています。しかし、なぜ?合成された関数テンプレートはあいまいではありません。これら2つのケースの違いは何ですか?

43
Barry

ClangはGCC互換です(そしてこれらの動作の両方に依存する既存のコードと互換性があります)。

[temp.deduct.type] p1を検討してください:

[...]推定値の代入後に、Pを作成するテンプレート引数値(型パラメーターの型、非型パラメーターの値、またはテンプレートパラメーターのテンプレート)を見つけようとします。 (推定Aと呼びます)、Aと互換性があります。

問題の核心は、ここで「互換性」が何を意味するかです。

関数テンプレートを部分的に順序付ける場合、Clangは単に両方向で推論します。控除が一方向で成功し、他の方向では成功しない場合、それは結果が「互換性がある」ことを意味すると想定し、それを注文結果として使用します。

ただし、クラステンプレートの半順序の特殊化を部分的に注文する場合、Clangは「互換性がある」を「同じ」を意味すると解釈します。したがって、一方から推定された引数をもう一方に置き換えると元の部分特殊化が再現される場合にのみ、1つの部分特殊化が別の部分特殊化よりも特殊化されていると見なされます。

これら2つのいずれかを他のコードと一致するように変更すると、かなりの量の実際のコードが破損します。 :(

5
Richard Smith