1

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();
    }
};
the_ducky
  • 165
  • 1
  • 12
  • "is it a clean design" might be a question more suitable for [CodeReview](http://codereview.stackexchange.com/). That said, some argue you should avoid private inheritance unless you need to use it (e.g. to override virtual functions); see [this SO question](http://stackoverflow.com/questions/7209019/private-inheritance-vs-composition-when-to-use-which) and this [C++FAQ](http://www.parashift.com/c++-faq-lite/priv-inherit-vs-compos.html) (both for `outbound`/`inbound` and `roundtrip`, composition can be used). – dyp Nov 15 '13 at 11:15

3 Answers3

2

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>)

dyp
  • 38,334
  • 13
  • 112
  • 177
  • @DyP that solves the problem without touching the policies, nor the user interface. your last idea, is it the same as my EDIT example in the OP? – the_ducky Nov 15 '13 at 11:05
  • @the_ducky Yes; though I stopped at the idea of inheritance and didn't think of forwarding the member functions with different names to avoid ambiguities. – dyp Nov 15 '13 at 11:09
1

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.

Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • private inheritance is considered by some more like a *is-implemented-in-terms-of*-relationship. It might be useful if the base classes are empty, for example. – dyp Nov 14 '13 at 16:48
  • Thanks, @BartoszKP, these are working solutions. However, i would prefer not to change the policies, since they are potentially created by the user and should remain as simple as possible. – the_ducky Nov 14 '13 at 16:50
  • @the_ducky No problem. You'll find DyP's answer more helpful. – BartoszKP Nov 14 '13 at 16:52
  • @DyP and BartoszKP : but then policy-based design always violates the _is-implemented-in-terms-of_-relationship, doesn't it? – the_ducky Nov 14 '13 at 17:09
  • @the_ducky Consider my last example - it uses the inheritance in an "OOP-correct" way, and it is considered policy based design. – BartoszKP Nov 14 '13 at 17:11
  • @BartoszKP I see, it's "OOP-correct". However one might argue that it's not policy-based design any more since there is no compile-time configurable host class template any more. – the_ducky Nov 15 '13 at 10:59
  • @the_ducky Conceptually it's the same as a strategy pattern, and that's how I look at it. The infrastructural mechanism behind a particular design doesn't really matter. But strictly speaking you're right, and by definition the solution without compile-time resolution is not policy-based design. – BartoszKP Nov 15 '13 at 11:09
0

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.

KitsuneYMG
  • 12,753
  • 4
  • 37
  • 58
  • @DyP yeah, except there's no C++11 tag on this. – KitsuneYMG Nov 14 '13 at 16:46
  • `Why not specialize on the case of identical base classes?` That actually sounds like a job for partial specialization: `template class roundtrip { /*...*/ };` – dyp Nov 14 '13 at 16:46
  • @KitsuneYMG: thanks, but spezialization is quite verbose here and - as you said - does not scale. nice to learn type_traits and std::conditional (DyP) though! – the_ducky Nov 15 '13 at 11:00
  • @the_ducky Take a look at Boost.MPL. You should be able to write a generic algorithm that can do this, replace Tn with T(n-1) if they are the same. It's just way more work than I was willing to put into a question that was explicitly about 2 base classes, especially as variadic templates is c++11 only and you're question is tagged [C++] – KitsuneYMG Nov 15 '13 at 15:29