14

I have this minimal expression template library with a multiplication, i.e.

template <typename T, typename U>
struct mul {
    const T &v1;
    const U &v2;
};

template <typename T, typename U>
mul<T, U> operator*(const T &one, const U &two) {
    std::cout << " called: mul<T, U> operator*(const T &one, const T &two)\n";
    return mul<T, U>{one, two};
}

and transpose, i.e.

template <typename T>
struct transpose {
    const T &t;
};

template <typename T>
transpose<T> tran(const T &one) {
    return transpose<T>{one};
}

I will introduce some types A and B, where the latter is a subclass of the former:

template <typename T>
struct A {
    T elem;
};

template <typename T>
struct B : A<T> {
    B(T val) : A<T>{val} {}
};

Then, I can call my expression template library as follows (with an overload for printing to std::cout):

template <typename T, typename U>
std::ostream &operator<<(std::ostream &os, const mul<T, U> &m) {
    os << " unconstrained template \n";
}

int main(int argc, char const *argv[]) {
    B<double> a{2};
    B<double> b{3};
    std::cout << tran(a) * b << "\n";
    return 0;
}

This gives me the output :

called: mul<T, U> operator*(const T &one, const T &two)
unconstrained template 

So far so good. Suppose now that I want a specialized treatment for 'transpose of a variable of type A<T> times a variable of type A<T> for some type T', as I had in my main. To this end, I will introduce

template <typename T>
T operator*(const transpose<A<T>> &one, const A<T> &two) {
    std::cout << " called: T operator*(const A<T> &one, const A<T> &two)\n";
    return one.t.elem * two.elem;
}

上記と同じmain関数を実行しても、上記と同じ出力が得られます ( unconstrained template)。transpose<B<double>>はと比較して完全に異なる型であるため、これは予想されることです。transpose<A<double>>そのため、オーバーロードの解決では の制約のないテンプレート バージョンが選択されoperator*ます。

(もちろん、変数定義mainAではなくに変更するとB、ADL は特殊な関数を呼び出し、出力はcalled: T operator*(const A<T> &one, const A<T> &two)and になります6)。

私は最近 SFINAE について学んだので、より具体的な乗算演算子を次のように変更すると、オーバーロードの結果が特殊な関数を選択するようになると予想していました。

template <typename T, typename V>
std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const transpose<V> &one,
                                                               const V &two) {
    std::cout << " called: std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const "
                 "transpose<V> &one, const V &two)\n";
    return one.t.elem * two.elem;
}

SFINAE'doperator*を使用しても、バージョンを取得できunconstrained templateます。どうして?より特化したテンプレート関数を呼び出すには、どのような変更を加える必要がありますか?

4

1 に答える 1

12

問題は、SFINAE オーバーロードで、T推定されないコンテキストで使用されることです。あなたは効果的にコンパイラに「の基本クラスであるTようなものが存在する場合はこれを有効にしてください」と尋ねています。実存的定量化は、求めているものが SFINAA 化できないことを示す良い指標です。A<T>V

ここで行ったように、制約のないテンプレートを無効にすると、これを自分で確認できます。これにより、コンパイラは、他の関数が許容されない理由を説明するように強制されます。

次のように、クラス(したがって)Tを通じて利用可能にすることで、これを解決できます。AB

template <typename T>
struct A {
    using Type = T;
    T elem;
};


template <typename V>
std::enable_if_t<std::is_base_of<A<typename V::Type>, V>::value, typename V::Type> operator*(const transpose<V> &one,
                                                               const V &two) {
    std::cout << " called: std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const "
                 "transpose<V> &one, const V &two)\n";
    return one.t.elem * two.elem;
}

【実例】

于 2019-05-29T12:31:48.587 に答える