使用继承的成员运算符而不是自由成员运算符
Use inherited member operator instead of free one
假设在命名空间ns
的某个地方定义了一个自由operator*
:
namespace ns {
struct Dummy {};
template <typename T>
Dummy operator*(T&&) {
return {};
}
}
在不同的位置,同一命名空间中有一个基类,它定义了成员operator*
:
namespace ns {
template <typename T>
struct Base {
T x;
T& operator*() {
return x;
}
};
}
以及从中衍生出的许多类型。它们的行为相同,但必须是不同的,因为其他地方的模板需要专门针对它们:
namespace ns {
template <typename T>
struct DerivedA : Base<T> {};
template <typename T>
struct DerivedB : Base<T> {};
template <typename T>
struct DerivedC : Base<T> {};
// etc
}
当我尝试在派生类上使用operator*
时:
ns::DerivedA<int> d;
*d = 42;
海湾合作委员会对我大喊"傻瓜!你没有int
分配给Dummy
!",这显然意味着使用自由operator*
而不是基类中的成员。
我完全无法控制自由运算符,也无法将派生类移动到不同的命名空间。
如何在不在每个派生类中复制operator*
的情况下解决此问题?
简短的回答,你可以这样做:
template <typename T>
struct DerivedA : Base<T> {
using Base<T>::operator*;
};
长答案:为了弄清楚要调用什么*d
,我们必须确定所有可行的函数 (§13.3.2):
从为给定上下文 (13.3.1) 构造的候选函数集中,一组可行函数为 选择,通过比较参数转换序列从中选择最佳函数 最佳拟合 (13.3.3)。可行函数的选择考虑了参数和函数之间的关系 转换序列排名以外的参数。
有两种:
template <typename T>
Dummy operator*(T&& );
T& Base::operator*();
为了确定选择哪一个,我们必须确定哪个"隐式转换序列"(§13.3.3.1)更好:
对于隐式转换序列是用于转换函数调用中的参数的转换序列 到被调用函数的相应参数的类型。
我们的第一个选项是"完全匹配",对于我们的第二个重载选项是 (§13.3.3.1.6):
当参数具有类类型且参数表达式具有派生类类型时, 隐式转换序列是从派生类到基类的派生到基的转换。 [ 注意:没有这样的标准转换;这种派生到基数的转换仅存在于 隐式转换序列。—尾注 ] 派生到基数的转换具有转换排名
转换序列的排名为 (§13.3.3.1.1.3):
表 12 中的每个转换还有一个关联的排名(完全匹配、促销或转换)。这些是 用于对标准转换序列进行排名 (13.3.3.2)。转换序列的秩由下式确定 考虑序列中每个转换的秩和任何引用绑定的秩(13.3.3.1.4)。如果 其中任何一个都有转换等级,序列有转换等级;否则,如果其中任何一个有促销 等级,序列有晋升等级;否则,序列具有完全匹配排名。
我不知道如何在此处插入表格。但基本上我们有一个"完全匹配"(调用Dummy operator*(T&&)
)和一个"转换"(调用T& Base::operator*
),因此"完全匹配"是"最佳可行函数"。以及 (§13.3.3.2):
如果只有一个可行函数比所有其他可行函数更好,那么它就是 通过过载分辨率选择的一个
这就是为什么首选Dummy operator*(T&& )
。
现在,为什么我的提案有效?在这种情况下,我们的两个选项是:
template <typename T>
Dummy operator*(T&& );
T& DerivedA<int>::operator*();
因此,我们有两个"完全匹配"候选 - 尽管其中一个是通过模板,选择更好可行函数的标准之一是 (§13.3.3.1):
给定这些定义,可行函数 F1 被定义为比另一个可行函数更好的函数 F2 如果 ...
- F1 是非模板函数,F2 是函数模板专用化
因此,在这种情况下,我们选择 DerivedA::operator*
.这就是你想要的。
我完全无法控制自由运算符,也无法将派生类移动到不同的命名空间。
如果无法从用户的角度将派生类移动到其他命名空间,但可以在代码本身中移动它们,则可以执行以下操作:
namespace ns {
namespace ns_ {
template <typename T>
struct Base {
T x;
T& operator*() { return x; }
};
template <typename T>
struct DerivedA : Base<T> {};
}
using namespace ns_;
}
- 在运算符重载定义中使用成员函数(const错误)
- 三向比较运算符成员与非成员实现
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 消除好友和成员二进制运算符的歧义
- 不允许运算符 const 参数调用 const 成员函数
- 有人可以用"显式运算符 const GUID_t&() const"来解释成员函数的函数吗?
- 为什么重载运算符<<打印特征类成员会导致段错误?
- 运算符重载作为成员函数
- 如何测试成员相等运算符?
- 如何处理运算符=中的常量成员?
- 将方法委托给成员的重写运算符>
- C++ 20 中的运算符 == 和 <=> 应该作为成员还是自由函数实现?
- 清除具有已删除赋值运算符的成员的类实例
- 运行时多态性 - 箭头运算符访问了错误的成员?
- 如何正确返回带有成员变量的重载运算符++?
- 使用 delete [] 运算符取消分配类中数据成员的内存
- 模板的赋值运算符(成员函数)实现的正确语法
- 模板类友元运算符成员函数
- 我只能有const重载的运算符成员函数
- 由算术运算符成员函数修改的动态数组的复制构造函数