我在使用Apple LLVM编译器(XCode 4.5.2附带)时遇到问题,同时使用GCC正确运行。更重要的是比关于编译器问题的辩论(我认为GCC是正确的),这引发了关于重载运算符时模板特化的解决顺序的问题[1]。
考虑一个简单的矩阵类template <class T> class matrix_t
,其中一个特征定义了乘法的结果类型(带有标量,矩阵或向量)。这将类似于以下代码:
template <class T, class U, class Enable = void>
struct matrix_multiplication_traits {
//typedef typename boost::numeric::conversion_traits<T,U>::supertype type;
typedef double type;
};
template <class T, int N>
struct vectorN {
std::vector<T> vect;
vectorN() : vect(N) { for(int i = 0; i < N; i++) vect[i] = i; }
};
template <class T>
class matrix_t {
std::vector<T> vec;
public:
matrix_t<T> operator*(matrix_t<T> const& r) const {
std::cout << "this_type operator*(this_type const& r) const" << std::endl;
return r;
}
template <class U>
matrix_t<typename matrix_multiplication_traits<T, U>::type>
operator*(U const &u) const {
std::cout << "different_type operator*(U const &u) const" << std::endl;
return matrix_t<typename matrix_multiplication_traits<T, U>::type>();
}
};
还要考虑operator*
的{{1}}的专业化:
vectorN
并考虑一个简单的测试程序:
template <class T, class U, int N>
//vectorN<typename matrix_multiplication_traits<T,U>::type, N>
vectorN<double, N>
operator*(matrix_t<T> const&m, vectorN<U, N> const&v)
{
std::cout << "vectorN operator*(matrix, vectorN)" << std::endl;
//return vectorN<typename matrix_multiplication_traits<T,U>::type, N>();
return vectorN<double, N>();
}
“有问题的行”在GCC上运行全局定义的int main(int argc, const char * argv[])
{
matrix_t<double> test;
vectorN<double, 10> my_vector;
test * my_vector; // problematic line
return 0;
}
,在LLVM上运行operator*(matrix_t<T> const&, vectorN<U, N> const&)
。所以就像template <class U> matrix_t<T>::operator*(U const&) const
正在捕获所有模板特化查找一样。一个简单的“修复”是将全局matrix_t<T>::operator*(U const&)
移动到矩阵类中。
我首先认为这是特质课中的问题,可能过于复杂或错误(SFINAE)。但即使简化特征或完全禁用它(如在粘贴代码中)也会产生错误。然后我认为这是一个订单问题(就像在Herb Shutter的文章中一样),但是在operator*
声明和定义之间移动全局operator*
并不会改变事情。
当然,真正的问题是matrix_t
过于笼统,但是:
template <class U> matrix_t::operator*(U const&) const
的资格是什么?模板过载运算符专业化?这更像是模板专业化还是重载功能?那个基本定义是什么?由于它本质上是一个重载的运算符,我有点迷失。[1]我已经阅读了Herb Shutter关于模板专业化命令的建议。
答案 0 :(得分:2)
在类中定义的运算符重载是否优先于全局定义的运算符重载?
否即可。根据C ++ 11标准的第13.3.1 / 2段:
候选函数集可以包含要针对相同参数列表解析的成员函数和非成员函数。因此,参数列表和参数列表在此异构集中是可比较的,成员函数被认为具有额外的参数,称为隐式对象参数,其表示已调用成员函数的对象。出于重载解析的目的,静态和非静态成员函数都有一个隐式对象参数,但构造函数没有。
此外,标准成员函数中没有提到优先于非成员函数,反之亦然。
是标准涵盖的这类问题吗?
是。选择全局运算符(应该是)的原因是它是一个更专业的模板,而不是类中定义的函数模板:在模板参数推断之后,两者都是可以使vectorN<U, N> const&
和matrix_t<T> const&
与matrix_t<T> const& r
匹配,但前者比后者更专业,因此首选。
按照第13.3.3 / 1段的规定:
鉴于这些定义,如果对于所有参数i,ICSi(F1)不是比ICSi(F2)更差的转换序列,则可行函数F1被定义为比另一个可行函数F2更好的函数,然后:
[...]
- F1和F2是功能模板专业化,F1的功能模板比F2模板更专业,根据14.5.6.2中描述的偏序规则。
因此:
因此就像matrix_t :: operator *(U const&amp;)正在捕获所有模板特化查找
此行为符合错误的条件。但请注意,这些不是特化,而是重载。
最后:
operator * (matrix_t<T> const&, vectorN<U, N> const&)
的资格是什么?
我想你可以说它是一个(全局)运算符重载模板。 不是一种特殊化,因为模板函数特化具有不同的语法。因此,没有专门的主要模板。它只是一个函数模板,一旦实例化,就会生成乘法运算符的重载。