Reputation: 185
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();
}
};
Upvotes: 1
Views: 731
Reputation: 35921
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 A
s. Here surely it's not the case. The private inheritance "hack" makes this awkwardness invisible though, that's why it seems acceptable to me.
Upvotes: 1
Reputation: 12901
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.
Upvotes: 0
Reputation: 39151
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>
)
Upvotes: 2