【问题标题】:Strange error with a templated operator overload模板化运算符重载的奇怪错误
【发布时间】:2013-09-06 22:21:49
【问题描述】:

当我编译以下 sn-p 时,我在 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,那么我会得到关于另一个 operator > 函数的相同错误。以上在 gcc 4.4.6 和 VS2012 下编译良好。我的错误是什么?

谢谢。

【问题讨论】:

  • 它也可以用 gcc 4.8.1 编译:ideone.com/wG4Yzv(C++98 模式)和ideone.com/8fwOWq(C++11 模式)。你的 clang 版本是什么?
  • @gx_ 刚试了3.1和3.2,都出现了问题。
  • @BoBTFish 谢谢(太糟糕了,没有“在线 Clang”(不再))。我想知道非常简化的示例 ideone.com/0Aooxo 吗? 编辑: 哦,等等,gcc.godbolt.org 有 clang 3.0 :) 我的简化示例导致了同样的错误。 编辑(2):再次感谢。看起来像一个编译器错误...
  • 您的简化示例显示了相同的错误。删除不正确的会导致它成功。
  • 或者也许它实际上不是 Clang 中的错误,但其他编译器过于宽松:p 顺便说一句,我在某处读到最好避免使用这种不受约束的模板(尤其是对于运算符重载)...找到:A Modest Proposal: Fixing ADL (revision 2)

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


【解决方案1】:

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

回想一下名称查找、参数推导和过载解决的三位一体。第一步找到两个重载的operator&gt;。第二步推导出每个版本的模板参数。您可能会认为第二个重载将成为 SFINAE 规则 (14.8.2) 的牺牲品,因此只有第一个重载可以保留到第三步。但是,没有替换失败(例如缺少嵌套的 typedef),而是非法构造(参见前面提到的 13.5/1)。这本身就会导致程序格式错误 (14.3/6)

6 如果模板参数的使用在模板特化的实例化中产生了格式错误的构造,则程序是格式错误的。

在 14.8.3 中提到,对推导参数的检查发生在重载决议之前,因此您的首选运算符没有机会被选中。

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

【讨论】:

  • OP 的问题以operator &gt; 为例,但您的回答谈到operator &lt;,这只是一个错字吗?
  • DR2052DR1391 现在以两种不同的方式使 Clang 出错,但看起来他们的解决方案尚未实施。 Clang bug 13869 也是相关的。
【解决方案2】:

我必须承认,我真的不知道为什么 clang 在这里抱怨,它看起来像一个错误(编译器的)。顺便说一句,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 enable_if 是几行代码,你可以自己写,但是is_class需要编译器支持,所以没有c++11就不能用这个
【解决方案3】:

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

【讨论】:

  • 同时考虑两个模板时,应该忽略无效的模板实例化还是错误?
  • @John 不,只有替换失败不是错误(sfinae),参数推导期间的任何其他非法构造都是硬错误。有关详细信息,请参阅我的答案。
猜你喜欢
  • 2020-09-08
  • 1970-01-01
  • 1970-01-01
  • 2018-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多