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

v15*_*4c1 6 c++ templates clang++

在尝试使用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";
}
Run Code Online (Sandbox Code Playgroud)

该项目之前编译很好,我再次检查这个SSCCE几个编译器(gcc 4.5,4.6,4.7,4.8clang 3.3),他们都没有任何警告(编译它-Wall -Wextra -pedantic).所有编译器都设置为C++ 11/C++ 0x标准.添加构造函数来后ostr,它编译罚款甚至MSVC 20122010)

使两个operator<<非成员都表现出所有编译器的模糊性(如预期的那样)

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

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

  • 这段代码有效吗?会员运营商/功能是否应该比非会员运营商/功能更好地匹配,这是clang-3.4中的错误?
  • 或者所有其他编译器错误/过于宽容?

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

dyp*_*dyp 4

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

\n\n

[over.match.funcs]/2

\n\n
\n

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

\n
\n\n

/4

\n\n
\n

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

\n\n

\xe2\x80\x94 \xe2\x80\x9clvalue 对cv X \xe2\x80\x9d 的引用,用于不带ref 限定符或带& ref 限定符声明的函数

\n\n

\xe2\x80\x94 \xe2\x80\x9crvalue 对使用ref 限定符声明的函数的cv \xe2\x80\x9d 的引用 X&&

\n\n

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

\n
\n\n

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

\n\n
\n\n

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

\n\n
template<class T>\nostr& ostr::operator<<(ostr&, const T&);    // F1\n\ntemplate<class Stream>\nStream& ::operator<<(Stream&, const xy&);    // F2\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
ostr& ostr::operator<<(ostr&, const xy&);\nostr& ::    operator<<(ostr&, const xy&);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n
\n\n

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

\n\n
struct unique_T {};\nstruct unique_Stream {};\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我们通过用唯一类型替换模板参数来转换F1为(对于 也类似):F1\'Tunique_TF2

\n\n
ostr& ostr::operator<<(ostr&, const unique_T&);\nostr& ::    operator<<(unique_Stream&, const xy&);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
ostr a0;\nunique_T a1; // no reference, no cv-qualifier\n::operator<<(a0, a1) // does template argument deduction succeed?\n\n// reminder: signature of ::operator<<\ntemplate<class Stream>\nStream& ::operator<<(Stream&, const xy&);\n
Run Code Online (Sandbox Code Playgroud)\n\n

a0[with Stream= ]的推导成功,因此from 的ostr类型被认为至少与(的相应第一个参数的类型一样专业,作为模板参数)。我不确定第二个参数会发生什么,因为(它的类型是)的第二个参数不会发生任何推导。ostr&F1F2Stream&Streama1::operator<<const xy&

\n\n

现在我们使用参数 from 重复该过程F2\',并尝试推断 的模板参数F1

\n\n
unique_Stream a0;\nxy a1;\nostr::operator<<(a0, a1);\n\n// reminder: signature of ostr::operator<<\ntemplate<class T>\nostr& ostr::operator<<(ostr&, const T&);\n
Run Code Online (Sandbox Code Playgroud)\n\n

T这里,第一个参数没有发生演绎,但第二个参数 [with = ]发生并成功xy

\n\n

我的结论是,没有哪个函数模板比​​它更专业了。因此,重载解析应该由于不明确而失败。

\n