为什么“重载解决方案”偏向于不受约束的模板功能而不是更具体的模板功能?

Nib*_*bor 14 c++ generic-programming sfinae expression-templates

我有一个带有乘法的最小表达模板库,即

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};
}
Run Code Online (Sandbox Code Playgroud)

并转置,即

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

template <typename T>
transpose<T> tran(const T &one) {
    return transpose<T>{one};
}
Run Code Online (Sandbox Code Playgroud)

我将介绍一些type AB,其中后者是前者的子类:

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

template <typename T>
struct B : A<T> {
    B(T val) : A<T>{val} {}
};
Run Code Online (Sandbox Code Playgroud)

然后,我可以如下调用表达式模板库(带有重载到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;
}
Run Code Online (Sandbox Code Playgroud)

这给了我输出:

called: mul<T, U> operator*(const T &one, const T &two)
unconstrained template 
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。现在假设我要像“在类型中将类型A<T>的变量乘以A<T>某种类型的类型的变量”那样对它进行专门处理。为此,我将介绍Tmain

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;
}
Run Code Online (Sandbox Code Playgroud)

我运行与上述相同的main功能,但仍获得与上述(unconstrained template)相同的输出。这是意料之中的,因为transpose<B<double>>与相比是完全不同的类型transpose<A<double>>,因此重载分辨率选择的不受限制的模板版本operator*

(当然,如果我将变量定义更改mainA而不是B,则ADL调用专用函数,输出为called: T operator*(const A<T> &one, const A<T> &two)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;
}
Run Code Online (Sandbox Code Playgroud)

即使使用SFINAE,operator*我仍然可以获得该unconstrained template版本。怎么来的?我应该进行哪些更改才能调用更专业的模板函数?

Rei*_*ica 12

问题在于,在SFINAE重载中,T是在非推导上下文中使用的。您实际上是在询问编译器:“如果存在T这样A<T>的基类,则启用它V。” 存在的量化可以很好地表明您所要求的内容无法确定。

如果您像我在此处那样禁用无约束模板,则可以自己看到。这迫使编译器说明为什么其他函数不可接受。

您可以T通过在您的A(并因此B)类中使之可用来解决此问题,如下所示:

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;
}
Run Code Online (Sandbox Code Playgroud)

[现场示例]