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

时间:2013-11-13 14:11:49

标签: 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";
}

该项目之前编译得很好,我再次检查了这个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而不是模板参数)将解决模糊性并按预期工作,但这不是重点这里。

1 个答案:

答案 0 :(得分:2)

重载分辨率为成员函数添加了一个额外的参数,仅用于重载解析:

[over.match.funcs] / 2

  

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

/ 4

  

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

     

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

     对于使用X ref-qualifier 声明的函数,

- “对 cv &&的rvalue引用”      

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

遵循一些特殊规则,例如允许将rvalue绑定到此隐式对象参数(用于调用rvalues上的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更专业”(反之亦然):功能模板的部分排序。

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


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

struct unique_T {};
struct unique_Stream {};

然后我们将F1转换为F1',方法是将模板参数T替换为唯一类型unique_T(类似于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 [Stream = ostr]的扣除成功,因此来自ostr&的{​​{1}}类型被认为至少与F1一样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]会发生并成功。

我得出结论,没有任何功能模板更专业。因此,由于模糊性,重载决策应该失败