Question

i have a class template roundtrip which takes two policies. as long as they are different, everything is fine, but using one policy twice leads to compilation errors.

Example:

#include <iostream>

class walk {
protected:
    void move() {
        std::cout<<"i'm walking."<<std::endl;
    }
};

class car {
protected:
    void move() {
        std::cout<<"i'm driving in a car."<<std::endl;
    }
};

template<typename S, typename T>
class roundtrip : private S, private T {
public:
    void printSchedule(void) {
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        T::move();
    }
};

int main(void){
    roundtrip<walk,car> LazyTrip;
    LazyTrip.printSchedule();

    roundtrip<car,car> VeryLazyTrip; // ERROR: error: duplicate base type ‘walk’ invalid
    VeryLazyTrip.printSchedule();

    return 0;
}

how can that be resolved? or is there a better design to achieve the same behaviour?

EDIT: i added wrappers to the policies, the user interface does not change. what do you think about this solution, is it a clean design?

template<typename T>
class outbound : private T {
protected:
    void moveOutbound(void) {
        T::move();
    }
};

template<typename T>
class inbound : private T {
protected:
    void moveInbound(void) {
        T::move();
    }
};

template<typename S, typename T>
class roundtrip : private outbound<S>, private inbound<T> {
public:
    void printSchedule(void) {
        std::cout<<"away: ";
        this->moveOutbound();

        std::cout<<"return: ";
        this->moveInbound();
    }
};
Was it helpful?

Solution

Instead of inheriting, you could add data members to roundtrip, but the functions in walk and car are currently protected. If you cannot make the functions in walk and car public, you could:

  • befriend the roundtrip template

    class walk {
    protected:
        void move() {
            std::cout<<"i'm walking."<<std::endl;
        }
        template<class T, class S>
        friend class roundtrip;
    };
    
  • make the member functions accessible to roundtrip via a helper class:

    template<typename S, typename T>
    class roundtrip {
    private:
        struct helperT : private T
        {
        public: // or private + friend roundtrip
            using T::move;
            using T::T;
        } mT;
    
        struct helperS : private S
        {
        public:
            using S::move;
            using S::S;
        } mS;
    
    public:
        void printSchedule(void) {
            std::cout<<"away: ";
            mT.move();
    
            std::cout<<"return: ";
            mS.move();
        }
    };
    

    If the interface is the same for both types, you could use a helper class template instead of two helper classes:

    template<typename S, typename T>
    class roundtrip {
    private:
        template<class U>
        struct helper : private U
        {
        public: // or private + friend roundtrip
            using U::move;
            using U::U;
        };
    
        helper<S> mS;
        helper<T> mT;
    
    public:
        void printSchedule(void) {
            std::cout<<"away: ";
            mT.move();
    
            std::cout<<"return: ";
            mS.move();
        }
    };
    

    (it is also possible to inherit from a helper template specialization, e.g. class roundtrip : helperT<T>, helperS<S>)

OTHER TIPS

As noticed by DyP this is enough:

template<typename S, typename T>
class roundtrip {
    public:
    void printSchedule(void) {
        std::cout<<"away: ";
        awayPolicy.move();

        std::cout<<"return: ";
        returnPolicy.move();
    }

    private:
    T awayPolicy;
    S returnPolicy;
};

But this requires making move methods public. You can also make them static and public, so you won't need the instance fields:

class walk {
public:
    static void move() {
    std::cout<<"i'm walking."<<std::endl;
    }
};

// the same for car

template<typename S, typename T>
class roundtrip {
    public:
    void printSchedule(void) {
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        T::move();
    }
};

You can also consider creating a base class:

class travelPolicy
{
public:
    virtual void move() = 0;        
    //...
};

with car and walk deriving from it. Then your roundtrip class can accept both policies through a constructor and using them in printSchedule via pointers.

class roundtrip 
{
public:
    roundtrip(
        std::shared_ptr<travelpolicy> awayPolicy,
        std::shared_ptr<travelpolicy> returnPolicy)
    {
        this->awayPolicy = awayPolicy;
        this->returnPolicy = returnPolicy;
    }


    void printSchedule(void) 
    {
        std::cout<<"away: ";
        awayPolicy->move();

        std::cout<<"return: ";
        returnPolicy->move();
    }

private:
    std::shared_ptr<travelPolicy> awayPolicy;
    std::shared_ptr<travelPolicy> returnPolicy;
};

UPDATE (responding to OP's edit):

Your solution is fine in general, however it still seems an overuse of inheritance. When A inherits B it is a way of saying that B's are As. Here surely it's not the case. The private inheritance "hack" makes this awkwardness invisible though, that's why it seems acceptable to me.

Why not specialize on the case of identical base classes? If you can, I'd used boost's mpl::if_ instead of mine and c++11's or boost's type_trait's is_same. I don't have a compiler handy, so there may be some syntax issues below

#include <type_traits>

template<typename S, typename T>
class roundtrip_diff : private S, private T {
public:
    void printSchedule(void) {
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        T::move();
    }
};

template<typename S>
class roundtrip_same : private S {
public:
    void printSchedule(void) {
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        S::move();
    }
};

template<bool, typename True, typename False> struct if_{ typedef True type; };

template<typename True, typename False> struct if_<false,True,False> { typedef False type; };

template<typename A, typename B> struct is_same { enum{value=0}; };
template<typename A> struct is_same<A,A> { enum{value=1}; };

template<typename S, typename T>
class roundtrip : if_< is_same<S,T>::value, roundtrip_same<S>, roundtrip_diff<S,T> >::type { };

Obviously, a more elegant solution needs to be found when you allow an arbitrary number of arguments, but I think this should eliminate your error of duplicate bases.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top