为什么模板成员运算符会被选中,而不是外部友元运算符?

编程语言 2026-07-12

情形如下:

struct Builder
{
    Builder & append(int);
    Builder & append(double);
    // ... many other overloads for non-object types

    Builder & operator<<(auto && t) { return append(t); }
};

struct X
{
    friend Builder & operator<<(Builder & p, X const &) { /* … */ }
};

int main()
{
    Builder p;

    X x;
    p << x;
}

编译器(MSVC和 GCC)在选择成员运算符重载时,会倾向于使用 template 的那个,而不是 friend 的。为什么?

解决方案

要理解“为什么”,请记住,auto 参数类型本质上是模板的简写,因此你的代码大致相当于以下内容:

    Builder & append(int);
    Builder & append(double);

    template <class T>
    Builder & operator<<(T && t) { return append(t); } // [1]
};

由于它调用 appendT 必须是 intdouble,或某种能够隐式转换为 intdouble 的类型。由于不存在从 X 转换到 intdouble 的转换,因此会失败。我们可以通过添加从 X 转换到 int 的转换来让它起作用:

struct X
{
    friend Builder & operator<<(Builder & p, X const &) { return p << 42; }
    operator int() { return 1; }
    // conversion to `double` would work just as well.
};

...或者通过添加一个接收 Xappend 的重载来实现:

struct X;

struct Builder
{
    Builder & append(int);
    Builder & append(double);
    Builder & append(X const &);

    // ... many other overloads

    Builder & operator<<(auto && t) { return append(t); } // [1]
// ...

两种方式都能工作。在这种情况下,你可以同时拥有两者,但如果你得到的是等价的转换序列,就会产生歧义,编译就无法通过。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章