Use inherited member operator instead of free one

96 views Asked by At

Suppose there is a free operator* defined somewhere in the namespace ns:

namespace ns {
    struct Dummy {};

    template <typename T>
    Dummy operator*(T&&) {
        return {};
    }
}

In a different place there is a base class in the same namespace, that defines member operator*:

namespace ns {
    template <typename T>
    struct Base {
        T x;

        T& operator*() { 
            return x;
        }
    };
}

And a lot of types derived from it. They behave identically, but must be distinct, for elsewhere templates need to be specialized on them:

namespace ns {
    template <typename T>
    struct DerivedA : Base<T> {};

    template <typename T>
    struct DerivedB : Base<T> {};

    template <typename T>
    struct DerivedC : Base<T> {};

    // etc
}

When I try to use operator* on a derived class:

ns::DerivedA<int> d;
*d = 42;

GCC yells at me "FOOL! Thou shan't assign int to Dummy!", which apparently means that free operator* is used instead of the member one in the base class.

I have no control at all over free operator, and can't move derived classes to different namespace.

How can I fix this without duplicating operator* in each derived class?

2

There are 2 answers

0
Barry On BEST ANSWER

Short answer, you can do this:

template <typename T>
struct DerivedA : Base<T> {
    using Base<T>::operator*;
};

Long answer: in order to figure out what *d to call, we have to determine all the viable functions (§13.3.2):

From the set of candidate functions constructed for a given context (13.3.1), a set of viable functions is chosen, from which the best function will be selected by comparing argument conversion sequences for the best fit (13.3.3). The selection of viable functions considers relationships between arguments and function parameters other than the ranking of conversion sequences.

There are two:

template <typename T>
Dummy operator*(T&& );

T& Base::operator*();

In order to figure out which one to pick, we have to determine which "implicit conversion sequence" (§13.3.3.1) is better:

An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called.

which for our first option is an "Exact Match", and for our second overload option is (§13.3.3.1.6):

When the parameter has a class type and the argument expression has a derived class type, the implicit conversion sequence is a derived-to-base Conversion from the derived class to the base class. [ Note: There is no such standard conversion; this derived-to-base Conversion exists only in the description of implicit conversion sequences. —end note ] A derived-to-base Conversion has Conversion rank

The ranking of the conversion sequences is (§13.3.3.1.1.3):

Each conversion in Table 12 also has an associated rank (Exact Match, Promotion, or Conversion). These are used to rank standard conversion sequences (13.3.3.2). The rank of a conversion sequence is determined by considering the rank of each conversion in the sequence and the rank of any reference binding (13.3.3.1.4). If any of those has Conversion rank, the sequence has Conversion rank; otherwise, if any of those has Promotion rank, the sequence has Promotion rank; otherwise, the sequence has Exact Match rank.

I don't know how to insert the table here. But basically we have one "Exact Match" (to call Dummy operator*(T&&)) and one "Conversion" (to call T& Base::operator*), thus the "Exact Match" is the "best viable function". And (§13.3.3.2):

If there is exactly one viable function that is a better function than all other viable functions, then it is the one selected by overload resolution

That's why the Dummy operator*(T&& ) is preferred.

Now, why does my proposal work? In that case, our two options are:

template <typename T>
Dummy operator*(T&& );

T& DerivedA<int>::operator*();

So we have two "Exact Match" candidates - though one of them is through a template and one of the criteria for choose better viable functions is (§13.3.3.1):

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if ...

  • F1 is a non-template function and F2 is a function template specialization

Hence, in this case, we pick the DerivedA::operator*. Which is what you want.

0
Jiří Pospíšil On

I have no control at all over free operator, and can't move derived classes to different namespace.

If you cannot move the derived classes to a different namespace from the user's perspective but can move them in the code itself, you can do this:

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