8

在尝试 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.5)再次检查了这个 SSCCE 4.6,它们都在没有任何警告的情况下编译它(带)。所有编译器都设置为 C++11/C++0x 标准。将 ctor 添加到 之后,即使在and上也可以正常编译)4.74.8clang 3.3-Wall -Wextra -pedanticostrMSVC 20122010

将两者都operator<<设为非成员会在所有编译器中表现出歧义(如预期的那样)

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

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

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

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

4

1 回答 1

4

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

[over.match.funcs]/2

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

/4

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

—“对cv 的左值引用X”用于声明没有ref-qualifier& ref -qualifier 的函数

— 使用ref 限定符声明的函数的“对cv 的右值引用”X&&

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

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


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

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

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

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

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

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

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


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

struct unique_T {};
struct unique_Stream {};

然后我们通过将模板参数替换为唯一类型(类似地)转换F1为:F1'Tunique_TF2

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,因此ostr&from的类型F1被认为至少与 ( 的相应第一个参数的类型一样特化F2Stream&并且Stream是模板参数)。我不确定第二个参数会发生什么,因为(它是 type )a1的第二个参数没有进行推导。::operator<<const xy&

现在我们用参数重复这个过程,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] 会发生并成功。

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

于 2013-11-13T15:45:09.893 回答