一个模板操作符重载出现了奇怪的错误

10
当我编译以下代码片段时,使用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 > d而不是d > 10,那么我会得到关于另一个运算符 > 函数的同样错误。在gcc 4.4.6和VS2012下编译都没有问题。请问我的错误在哪里?

谢谢。


它在gcc 4.8.1下编译也很好:http://ideone.com/wG4Yzv(C++98模式)和http://ideone.com/8fwOWq(C++11模式)。你的clang版本是多少? - gx_
@gx_ 刚试了3.1和3.2,两个版本都出现了这个问题。 - BoBTFish
@BoBTFish 谢谢(太糟糕了,没有“在线Clang”(不再有了))。我想知道这个非常简化的例子http://ideone.com/0Aooxo?_编辑:_哦等等,在http://gcc.godbolt.org/上有clang 3.0 :)并且我的简化示例导致相同的错误。编辑(2):再次感谢。看起来像是编译器的一个bug... - gx_
你的简化示例展示了相同的错误。而移除错误后,它就成功了。 - BoBTFish
或许这并不是Clang的一个bug,而是其他编译器过于宽容了 :p 顺便说一下,我在某个地方读到过这样的无限制模板最好避免使用(特别是对于运算符重载)... 发现:A Modest Proposal: Fixing ADL (revision 2) - gx_
@gx_ 我的clang版本是3.2版(基于llvm 3.2)。这是Ubuntu 13.04的标准clang软件包。 - John
3个回答

7
Clang是正确的:运算符重载需要至少一个类或枚举类型参数,否则程序就是不合法的(13.5/1)。为了看到为什么会出现这个错误,我们必须解析更多的标准法律术语。
回想一下名称查找、参数推断和重载分辨率的三位一体。第一步找到两个重载的operator>。第二步为每个版本推断模板参数。你可能认为第二个重载将成为SFINAE规则(14.8.2)的受害者,因此只有第一个重载能在第三步中生存。然而,没有替换失败(例如缺少嵌套typedef),但存在非法构造(参见前面提到的13.5/1)。这本身使程序不合法(14.3/6)。

6如果模板参数的使用在模板特化的实例化中引起不合法的构造,则程序是不合法的。

在14.8.3中提到,在重载分辨之前对推导的参数进行了检查,因此您喜欢的运算符没有被选中的机会。
作为C++03的解决方法,您可以在Var<T>类模板中定义两个友元非模板operator>。这些将被注入到周围(在此示例中为全局)命名空间作为具有一个类类型参数的非模板函数,因此不应出现上述错误。

OP的问题以operator >为例,但是你的回答谈到了operator <,这只是一个打字错误吗? - greatwolf
1
DR2052DR1391现在让Clang以两种不同的方式出错,但看起来它们的解决方案尚未实施。 Clang bug 13869也是相关的。 - bogdan

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);
}

很遗憾,我只能使用C++03。如果我错了,请纠正我,但是std::enable_if、std::is_class等都是C++11的构造,所以我不能使用它们。 - John
@John,enable_if 是几行代码,你可以自己编写,但 is_class 需要编译器支持,所以如果没有 c++11,你不能使用它。 - TemplateRex

0

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


考虑到两个模板,无效的模板实例化应该被忽略还是视为错误? - John
@John 不,只有替换失败不是错误(sfinae),在参数推导期间的任何其他非法构造都是硬错误。请参阅我的答案以获取更多详细信息。 - TemplateRex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接