为什么 Overload Resolution 更喜欢不受约束的模板函数而不是更具体的模板函数?
Why is Overload Resolution favoring unconstrained template function over a more specific one?
我有这个带乘法的最小表达式模板库,即
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};
}
并转置,即
template <typename T>
struct transpose {
const T &t;
};
template <typename T>
transpose<T> tran(const T &one) {
return transpose<T>{one};
}
我会介绍一些类型A
和B
,其中后者是前者的子类:
template <typename T>
struct A {
T elem;
};
template <typename T>
struct B : A<T> {
B(T val) : A<T>{val} {}
};
然后,我可以如下调用我的表达式模板库(带有用于打印到 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;
}
这给了我输出:
called: mul<T, U> operator*(const T &one, const T &two)
unconstrained template
到目前为止一切顺利。假设现在我想要 'transpose of a variable of type A<T>
times a variable of type A<T>
for some type T
' 的专门治疗,就像我在 main
中所做的那样。为此,我将介绍
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*
的无约束模板版本。
(当然,如果我将 main
中的变量定义更改为 A
而不是 B
,ADL 调用专用函数,输出为 called: T operator*(const A<T> &one, const A<T> &two)
和 6
).
我最近了解了 SFINAE,所以我预计对更具体的乘法运算符的以下更改会导致 select 专用函数的重载结果:
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 的 operator*
我仍然得到 unconstrained template
版本。怎么会?我应该做哪些更改才能调用更专业的模板函数?
问题是在 SFINAE 重载中,T
在 non-deduced 上下文中使用。您实际上是在询问编译器:"Enable this if there exists a T
such that A<T>
is a base class of V
." 存在量化是一个很好的指标,表明您要求的内容不能是 SFINAEd。
如果像我一样禁用不受约束的模板,您可以自己看到这一点 here。这迫使编译器阐明为什么另一个函数不可接受。
您可以通过 A
(因此 B
)类 提供 T
来解决此问题,如下所示:
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;
}
我有这个带乘法的最小表达式模板库,即
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};
}
并转置,即
template <typename T>
struct transpose {
const T &t;
};
template <typename T>
transpose<T> tran(const T &one) {
return transpose<T>{one};
}
我会介绍一些类型A
和B
,其中后者是前者的子类:
template <typename T>
struct A {
T elem;
};
template <typename T>
struct B : A<T> {
B(T val) : A<T>{val} {}
};
然后,我可以如下调用我的表达式模板库(带有用于打印到 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;
}
这给了我输出:
called: mul<T, U> operator*(const T &one, const T &two)
unconstrained template
到目前为止一切顺利。假设现在我想要 'transpose of a variable of type A<T>
times a variable of type A<T>
for some type T
' 的专门治疗,就像我在 main
中所做的那样。为此,我将介绍
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*
的无约束模板版本。
(当然,如果我将 main
中的变量定义更改为 A
而不是 B
,ADL 调用专用函数,输出为 called: T operator*(const A<T> &one, const A<T> &two)
和 6
).
我最近了解了 SFINAE,所以我预计对更具体的乘法运算符的以下更改会导致 select 专用函数的重载结果:
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 的 operator*
我仍然得到 unconstrained template
版本。怎么会?我应该做哪些更改才能调用更专业的模板函数?
问题是在 SFINAE 重载中,T
在 non-deduced 上下文中使用。您实际上是在询问编译器:"Enable this if there exists a T
such that A<T>
is a base class of V
." 存在量化是一个很好的指标,表明您要求的内容不能是 SFINAEd。
如果像我一样禁用不受约束的模板,您可以自己看到这一点 here。这迫使编译器阐明为什么另一个函数不可接受。
您可以通过 A
(因此 B
)类 提供 T
来解决此问题,如下所示:
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;
}