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

时间:2014-11-22 15:02:35

标签: c++ inheritance operator-overloading overload-resolution

假设在命名空间operator*中的某处定义了免费的ns

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;
海湾合作委员会对我大吼大叫&#34;傻瓜!您没有将int分配给Dummy!&#34;,这显然意味着使用免费operator*而不是基类中的成员。

我对free运算符没有任何控制权,也无法将派生类移动到不同的名称空间。

如何解决此问题而不在每个派生类中复制operator*

2 个答案:

答案 0 :(得分:2)

简短的回答,你可以这样做:

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*。这是你想要的。

答案 1 :(得分:0)

  

我对free运算符没有任何控制权,也无法将派生类移动到不同的名称空间。

如果你不能从用户的角度将派生类移动到不同的命名空间,但可以在代码本身中移动它们,你可以这样做:

 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_;
 }