Search code examples
inheritancec++11operator-overloadingcrtpenable-if

enable_if allowing base class only


I'm currently implementing some CRTP with a base class template<class CRTP> Base and derived classes Derived1 : public Base<Derived1>, Derived2 : public Base<Derived2>...

The mathematical operators are defined in Base and are of the type CRTP Base<CRTP>::operator+(const CRTP& rhs) that means that we can add a Derived1 to a Derived1 but not a Derived2 to a Derived1.

Furthermore, I have defined the operator Base<CRTP>& Base<CRTP>::operator()() which means that Derived1() will return Base<Derived1>&.

I wonder if there is a solution to do the following :

Derived1 = Derived1 + Derived1 : OK
Derived2 = Derived2 + Derived2 : OK
Derived1 = Derived1 + Derived2 : NOT OK
Derived1 = Derived1() + Derived2() : OK

According to the last two lines :

  • I prevent the user to make a mistake
  • But if he really wants to do this operation he can "cast" the derived types to the base type and it will work

The only thing I need to do this is to define an operator like that :

template<class CRTP0, class = typename std::enable_if</* SOMETHING */>::type> 
Base<CRTP> Base<CRTP>::operator+(const Base<CRTP0>& rhs)

In the enable_if I would like something that is :

  • true : if rhs is of the Base type
  • false : if rhs is a Derived type

Does a such thing exist ? Do you have an other solution in mind ?

Thank you very much !


Solution

  • The /* SOMETHING */ can be easily archived using

    1. std::is_same for the 'false' part of Derived and
    2. A helper class for the 'true' part of Base

    The helper class is to determine whether a class is exactly a Base<?>:

    template <typename> struct IsBase : std::false_type {};
    ...
    template <typename X> struct IsBase<Base<X>> : std::true_type {};
    

    and then we could fill in that /* SOMETHING */ with:

    std::is_same<Other, Self>::value || IsBase<Other>::value
    

    Note that this allows Derived1 + Derived2().


    Example: http://ideone.com/OGt0Q

    #include <type_traits>
    
    template <typename> struct IsBase : std::false_type {};
    
    template <typename Self>
    struct Base {
        Base& operator()() {
            return *this;
        };
    
        template <typename Other,
                  typename = typename std::enable_if<std::is_same<Other, Self>::value
                                                  || IsBase<Other>::value>::type>
        Self operator+(const Other& other) const {
            return static_cast<const Self&>(*this);
        }
    };
    
    template <typename X> struct IsBase<Base<X>> : std::true_type {};
    
    
    struct D1 : Base<D1> {};
    struct D2 : Base<D2> {};
    
    
    int main() {
        D1 d1;
        D2 d2;
        d1 + d1; // ok
        d2 + d2; // ok
        d1() + d2(); // ok
        d1 + d2; // error
    }