使用继承的成员运算符而不是自由成员运算符

Use inherited member operator instead of free one

本文关键字:运算符 成员 自由 继承      更新时间:2023-10-16

假设在命名空间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_;
 }