模板化运算符重载的奇怪错误

时间:2013-09-03 15:46:47

标签: c++ templates operator-overloading clang sfinae

当我编译以下代码片段时,我得到了一个带有clang的编译器错误,但没有使用g ++ / MSVC:

#include <string>

template<typename T> struct Const { 
    explicit Const(T val) : value(val) {}
    T value;
};

template<typename T> struct Var {
    explicit Var(const std::string &n) : name(n) {}

    std::string name;
};

template<typename L, typename R> struct Greater {
    Greater(L lhs, R rhs) : left(lhs), right(rhs) {}

    L left;
    R right;
};

template<typename L>
Greater<L, Const<int> > operator > (L lhs, int rhs) { 
    return Greater<L, Const<int> >(lhs, Const<int>(rhs));
}

template<typename R>
Greater<Const<int>, R> operator > (int lhs, R rhs) { 
    return Greater<Const<int>, R>(Const<int>(lhs), rhs);
}

Var<double> d("d");

int main() {
     d > 10;
     return 0;
}

报告的错误如下:

error: overloaded 'operator>' must have at least one parameter of
      class or enumeration type
Greater<Const<int>, R> operator > (int lhs, R rhs) { 
                       ^
./val.h:31:24: note: in instantiation of function template specialization
      'operator><int>' requested here
Greater<Const<int>, R> operator > (int lhs, R rhs) { 
                       ^
1 error generated.

是关于未使用的运算符函数。相反,如果我写10&gt; d而不是d> 10,然后我得到关于其他运算符的相同错误&gt;功能。以上编译在gcc 4.4.6和VS2012下正常。我的错是什么?

谢谢。

3 个答案:

答案 0 :(得分:7)

Clang是对的:运算符重载至少需要一个类或枚举类型参数,否则程序格式不正确(13.5 / 1)。要了解为什么会出现此错误,我们必须解析更多标准法律问题。

回想起名称查询,论证扣除和重载决议的三位一体。第一步找到两个重载的operator>。第二步推导出每个版本的模板参数。您可能会认为第二次超载会成为SFINAE规则(14.8.2)的受害者,因此只有第一次超载才会存活到第三步。但是,没有替换失败(例如缺少嵌套的typedef),而是非法的构造(参见前面提到的13.5 / 1)。这本身就使得该计划形成不良(14.3 / 6)

  

6如果在模板专业化的实例化中使用template-argument会导致格式错误的构造,那么该程序就会形成错误。

在14.8.3中,提到对推断出的参数的这种检查在重载解析之前发生,因此你的首选运算符不可能被选中。

作为C ++ 03的解决方法,您可以在operator>类模板中定义两个朋友非模板Var<T>。这些将作为具有一个类类型参数的非模板函数注入到周围(全局,在此示例中)命名空间中,因此不应发生上述错误。

答案 1 :(得分:0)

我必须承认,我真的不知道为什么clang在这里抱怨,它看起来像一个bug(编译器的)。顺便说一下,clang 3.3也表现出这个问题。

您可以使用SFINAE来抑制它:

template<typename L>
typename std::enable_if<std::is_class<L>::value || std::is_enum<L>::value,
                        Greater<L, Const<int>>>::type
operator > (L lhs, int rhs) { 
    return Greater<L, Const<int> >(lhs, Const<int>(rhs));
}

template<typename R>
typename std::enable_if<std::is_class<R>::value || std::is_enum<R>::value,
                        Greater<Const<int>,R>>::type
operator > (int lhs, R rhs) { 
    return Greater<Const<int>, R>(Const<int>(lhs), rhs);
}

答案 2 :(得分:0)

这看起来像g ++和VS中的一个错误。在您的示例中,您的类型Rint(因为右侧操作数为int)。然后,这将使函数Greater<Const<int>, R> operator > (int lhs, int rhs) 的签名与内置operator< ints 的签名相同(参数)。请注意,在决定使用哪个operator>时,它必须考虑两个模板(并尝试为每个模板分别推断类型):它不能只看其中一个并决定忽略另一个。