【问题标题】:Templated operator overload resolution, member vs non-member function模板化运算符重载决议,成员与非成员函数
【发布时间】:2013-11-13 14:11:49
【问题描述】:

在尝试 clang-3.4(从 gi​​t 编译)时,它未能编译我的一个项目,抱怨在解决重载运算符时存在歧义。 我发现有两个模板化的运算符,一个被声明为成员函数,另一个被声明为非成员函数,两者看起来都非常匹配。

以下SSCCE演示情况:

#include <iostream>

struct ostr {
        std::ostream& s;

        template<class T>
        ostr& operator<<(const T& x) { s << x; return *this; }
};

struct xy {
        double x, y;
};

template<class Stream>
Stream& operator<<(Stream& s, const xy& x) {
        s << "[" << x.x << ", " << x.y << "]";
        return s;
}

int main() {
        ostr os{std::cout};
        xy x{4, 5};
        os << "Value is: " << x <<"\n";
}

该项目之前编译得很好,我用几个编译器(gcc 4.54.64.74.8clang 3.3)再次检查了这个 SSCCE,所有编译器都在没有任何警告的情况下编译它(使用 @ 987654329@)。所有编译器都设置为 C++11/C++0x 标准。 将ctor添加到ostr后,即使在MSVC 20122010上也能正常编译)

operator&lt;&lt;s 都设为非成员会在所有编译器中表现出歧义(正如预期的那样)

查看标准草案(N3242N3690)后,我没有找到任何使成员函数/运算符比非成员函数/运算符更匹配的东西。

所以我没能证明clang-3.4 是错的,我想知道谁是对的。 因此我的问题是:

  • 此代码有效吗?成员运算符/函数是否应该比非成员运算符/函数更好地匹配,这是 clang-3.4 中的一个错误?
  • 还是所有其他编译器都错误/过于宽松?

我知道将第二个 operator&lt;&lt; 更改为非模板函数(使用 std::ostream 而不是模板参数)将解决歧义并按预期工作,但这不是重点。 em>

【问题讨论】:

  • +1 因为这是一个有趣的问题;但如果可以的话,我会再次 +1,以确保您的清晰和彻底!
  • “成员运算符/函数是否应该比非成员运算符/函数更好匹配” 不,但它们作为附加参数获得对其类类型的引用,只是为了解决重载问题。
  • 可能与this clang bug相关
  • DyP:这个错误应该在 r190470 中得到修复。我用当前版本(r194576,一小时前提交)对其进行了测试,结果仍然相同。
  • 尝试使用 before r190470 版本,例如 coliru 使用的 184460:它的行为与其他编译器一样。

标签: c++ templates clang++


【解决方案1】:

重载解析为成员函数添加了一个额外的参数,只是为了重载解析的目的:

[over.match.funcs]/2

候选函数集可以包含要针对同一个参数列表解析的成员函数和非成员函数。为了使实参和形参列表在这个异构集合中具有可比性,成员函数被认为有一个额外的形参,称为隐式对象形参,它表示已为其调用成员函数的对象。

/4

对于非静态成员函数,隐含对象参数的类型为

—“对 cv X 的左值引用”,对于没有 ref-qualifier 或使用 &amp; ref-qualifier 声明的函数>

—“对 cv X 的右值引用”,用于使用 &amp;&amp; 声明的函数 ref-qualifier

其中X 是函数所属的类,cv 是成员函数声明上的cv-限定。

遵循一些特殊规则,例如允许将右值绑定到此隐式对象参数(用于在右值上调用不带 ref 限定符的成员函数,例如 ostr{std::cout}&lt;&lt;"hello")。


函数签名包括我们需要比较重载解析的隐式对象参数是:

template<class T>
ostr& ostr::operator<<(ostr&, const T&);    // F1

template<class Stream>
Stream& ::operator<<(Stream&, const xy&);    // F2

替换os &lt;&lt; x后,我们得到相同的签名:

ostr& ostr::operator<<(ostr&, const xy&);
ostr& ::    operator<<(ostr&, const xy&);

因此,只有 [over.match.best]/1 中的“决胜局”之一可以解决歧义。确实,可以应用,即“F1F2 更专业”(反之亦然):函数模板的部分排序。

注意添加隐式对象参数的过程在偏序描述[temp.func.order]/3中再次指定。


对于F1F2(如上定义)的部分排序,我们首先创建两个唯一类型:

struct unique_T {};
struct unique_Stream {};

然后我们通过将模板参数T 替换为唯一类型unique_TF1 转换为F1'F2 也是如此):

ostr& ostr::operator<<(ostr&, const unique_T&);
ostr& ::    operator<<(unique_Stream&, const xy&);

转换后的函数F1'的参数现在用来尝试推导未转换的F2的模板参数:

ostr a0;
unique_T a1; // no reference, no cv-qualifier
::operator<<(a0, a1) // does template argument deduction succeed?

// reminder: signature of ::operator<<
template<class Stream>
Stream& ::operator<<(Stream&, const xy&);

a0 [with Stream = ostr] 推演成功,因此来自F1 的类型ostr&amp; 被认为至少与F2 的相应第一个参数的类型一样特化(Stream&amp;Stream 是模板参数)。我不确定第二个参数a1 会发生什么,因为::operator&lt;&lt; 的第二个参数(它的类型为const xy&amp;)没有进行任何推导。

现在我们用F2'的参数重复这个过程,并尝试推导出F1的模板参数:

unique_Stream a0;
xy a1;
ostr::operator<<(a0, a1);

// reminder: signature of ostr::operator<<
template<class T>
ostr& ostr::operator<<(ostr&, const T&);

这里,第一个参数不进行推导,但第二个参数发生并成功 [with T = xy]。

我的结论是没有功能模板更专业。因此,由于歧义,重载决议应该失败

【讨论】:

  • 我确定吗?否。请添加任何有助于澄清问题的提示。
  • 非常感谢您的逐步指导。你让[over.match.best]对我来说更清楚了:)。所以代码应该是无效的并且clang(在r190470之后)是唯一正确的吗?肯定是这样的……
猜你喜欢
  • 2011-06-05
  • 1970-01-01
  • 2010-12-26
  • 1970-01-01
  • 2013-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多