繁体   English   中英

模板化运算符重载解决方案,成员与非成员函数

[英]Templated operator overload resolution, member vs non-member function

尝试使用clang-3.4(从git编译)时,它无法编译我的一个抱怨歧义的项目,同时解决了重载的运算符。 我发现有两个模板化的运算符,其中一个被声明为成员函数,另一个被声明为非成员函数,两者看起来都很好地匹配。

以下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";
}

该项目编译罚款之前,我再次检查这个SSCCE几个编译器( gcc 4.54.64.74.8clang 3.3 ),他们都编译没有任何警告(与-Wall -Wextra -pedantic )。 所有编译器均设置为C ++ 11 / C ++ 0x标准。 将ctor添加到ostr ,即使在MSVC 20122010上,它也可以很好地编译)

使两个operator<<都成为非成员在所有编译器中都表现出歧义(如预期)

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

因此,我未能证明clang-3.4是错误的,我想知道谁是正确的。 因此,我的问题是:

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

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

重载解析只是为了重载解析的目的而向成员函数添加了一个附加参数:

[over.match.funcs] / 2

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

/ 4

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

—“不带ref限定符或带有& ref限定符声明的函数的“对cv X左值引用”

- “右值参考CV X ”为功能与声明&& REF-限定符

其中X是该函数是成员的类,而cv是成员函数声明上的cv -qualification。

遵循一些特殊规则,例如,允许将右值绑定到此隐式对象参数(用于在右值上调用不带ref限定符的成员函数,例如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的描述中再次指定添加隐式对象参数的过程。


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

struct unique_T {};
struct unique_Stream {};

然后,通过将模板参数T替换为唯一类型unique_T (对于F2同样),将F1转换为F1'

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 [ Stream = ostr ],因此F1 ostr&类型被认为至少与F2的相应第一个参数的类型一样专门化( Stream&Stream是模板参数)。 我不确定第二个参数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&);

在此,第一个自变量没有演绎,但第二个自变量[ T = xy ]则成功推演。

我得出结论,没有功能模板更专业。 因此,由于歧义,过载解析将失败

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM