运算符重载、名称解析和命名空间
Operator overloading, name resolution and namespaces
我想对涉及 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*
的唯一参数是 float
和 MyDeriv
在命名空间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(); }
}
- 从父命名空间重载类型
- 在 C++17 中的命名空间和子命名空间中重载运算符是不明确的
- Visual Studio 无法解决类和命名空间中重载的明确函数
- 在类设计中查找外部命名空间中的重载运算符
- 重载运算符 + 用于向量:命名空间标准
- 在命名空间中使用指令和函数重载
- 使用类指针重载C++命名空间函数模板专用化替代方法?
- 为什么找不到使用命名空间中定义的类型实例化的 std::weak_ptr 的重载运算符==?
- 在命名空间内的类中使用带有运算符重载的字符串流时"no match for ‘operator>>’"
- 在命名空间内的 lambda 中使用时未找到运算符重载
- 从类方法调用命名空间中名为 Same 的函数时,重载解析失败
- 为命名空间中的类重载运算符<<时发出警告
- 为在与类方法中的类相同的命名空间中定义的结构调用重载运算符
- 在全局命名空间中重载不依赖于用户定义类型的标准定义类型的运算符是否格式正确?
- 基于 SFINAE 的跨命名空间的运算符重载
- 如何在不同的命名空间中指定重载运算符
- 为什么使用范围解析运算符会更改调用全局命名空间中的哪个重载模板?
- C++无法重载类(和命名空间)中的函数
- C++:在我的命名空间中使用重载运算符
- 命名空间 + 重载 std::ostream <<运算符