运算符重载、名称解析和命名空间

Operator overloading, name resolution and namespaces

本文关键字:命名空间 重载 运算符      更新时间:2023-10-16

我想对涉及 ADL、命名空间和运算符重载的令人费解的情况有所了解。

让Foo是一个库,它在自己的命名空间中定义一个类(Deriv),以及一个返回另一个类的模板化operator *

namespace Foo {
    class Deriv {};
    class Another {};
    template <typename T>
    Another operator* ( T x, const Deriv& d ) { return Another();}
}

现在我在自己的库 Bar 中使用 Foo 的类,它定义了另一个operator *,这次仅用于float

namespace Bar {
    typedef Foo::Deriv MyDeriv;
    MyDeriv operator* (float x, const MyDeriv& d) { return MyDeriv();}
}

我观察到编译器行为的差异取决于是否在namespace Bar内部。

这个函数(Bar::f1)使用operator *的第二个版本进行编译:

namespace Bar {
    void f1() {
        Bar::MyDeriv a;
        Bar::MyDeriv b = 3.f * a;
    }
} 

而命名空间 Bar (f2()) 之外的同一个函数无法编译,因为编译器只尝试使用 Foo::operator* 而无法猜测它必须使用 Bar::operator*

void f2() {
    Bar::MyDeriv a; 
    Bar::MyDeriv b = 3.f * a; // Error : cannot convert Foo:Another to Bar::Myderiv
}

你可以在这里看到代码直播:http://ideone.com/pkPeOY

现在,如果Foo::operator*没有模板化并定义为Foo::operator*(float, const Deriv& d);两个函数都无法编译并出现相同的错误(不明确的运算符重载),如下所示: http://ideone.com/wi1EWS

所以,面对这种情况,这就是让我感到困惑的地方

  • 模板化的情况下,当编译f2时,编译器考虑使用Foo::operator*但不使用Bar::operator*,而在非模板化的情况下,它考虑同时使用两者(并且由于歧义而拒绝进一步)。是什么让编译器的行为不同?

  • 我的库 Bar 的用户将在 Bar:: 命名空间之外,但我希望使用Bar::operator*,而不是Foo::operator*。我考虑过显式调用 Bar::operator*(3.f,a) ,这很丑陋,或者在全局命名空间中插入我自己的运算符,我认为这是一件坏事。我是否缺少一个选项,还是我做错了什么?

在模板化的情况下,当编译f2时,编译器考虑使用Foo::operator*而不是Bar::operator*,而在非模板化的情况下,它考虑同时使用两者(并且由于歧义而拒绝进一步)。是什么让编译器的行为不同?

在这两种情况下,编译器都会考虑同时使用两者,但在模板化operator*的情况下,调用并不模棱两可,因为有一个非模板化函数,其参数类型与参数完全匹配(尝试将3.f替换为3.您将看到找到模板化版本)。通常:

template <typename T>
void g (T) { }
void g (float) { }
g(0.f); // Ok, the overload for float is preferred over the templated version

我的库 Bar 的用户将在 Bar:: 命名空间之外,但我希望使用Bar::operator*,而不是 Foo::operator*。我考虑过显式调用 Bar::operator*(3.f,a) ,这很丑陋,或者在全局命名空间中插入我自己的运算符,我认为这是一件坏事。我是否缺少一个选项,还是我做错了什么?

不幸的是,ADL 不会找到您的重载,因为 operator* 的唯一参数是 floatMyDeriv 在命名空间Foo 中定义。一种可能的方法是从Foo::Deriv继承:

namespace Bar {
    struct MyDeriv: public Foo::Deriv {};
    MyDeriv operator* (float x, const MyDeriv& d) { return MyDeriv();}
}

另一个方法是在 Foo 命名空间中声明 operator* 的重载:

namespace Bar {
    typedef Foo::Deriv MyDeriv;
}
namespace Foo {
    Bar::MyDeriv operator* (float x, const Bar::MyDeriv& d) { return Bar::MyDeriv(); }
}