Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile-time interface implementation check in C++

I'm using pseudo-interfaces in C++, that is, pure abstract classes. Suppose I have three interfaces, IFoo, IBar and IQuux. I also have a class Fred that implements all three of them:

interface IFoo
{
    void foo (void);
}   

interface IBar
{
    void bar (void);
}

interface IQuux
{
    void quux (void);
}   

class Fred : implements IFoo, IBar, IQuux
{
}

I want to declare a method that accepts any object that implements IFoo and IBar - a Fred would work, for example. The only compile-time way to do this I can imagine is to define a third interface IFooAndBar that implements both, and redeclare Fred:

interface IFooAndBar : extends IFoo, IBar
{   
}

class Fred : implements IFooAndBar, IQuux
{
}

Now I can declare my method as receiving a IFooAndBar*. So far so good.


However, what happens if I also want a different method that accepts IBar and IQuux? I tried declaring a new interface IBarAndQuux and declaring Fred as inheriting both :

class IFooAndBar : IFoo, IBar
{
};


class IBarAndQuux : IBar, IQuux
{
};


class Fred : IFooAndBar, IBarAndQuux
{
};

This works when I pass Fred as a IFooAndBar to a method; however, when I try to call Fred::bar() directly, gcc complains:

error: request for member ‘bar’ is ambiguous
error: candidates are: void IBar::bar()
error:                 void IBar::bar()

which makes this solution more or less useless.


My next attempt was to declare Fred as inheriting from the three individual interfaces, and making the method accept one of the hybrid interfaces as a parameter :

class Fred : public IFoo, public IBar, public IBaz
{

};

void doTest (IBarAndBaz* pObj)
{
    pObj->bar();
    pObj->baz();
}

When I try to pass Fred as the IBarAndBaz* parameter, I get an error, as expected:

error: cannot convert ‘Fred*’ to ‘IBarAndBaz*’ for argument ‘1’ to ‘void doTest(IBarAndBaz*)’

dynamic_cast<> also produces an error (which I don't understand)

error: cannot dynamic_cast ‘pFred’ (of type ‘class Fred*’) to type ‘class IBarAndBaz*’ (source type is not polymorphic)

Forcing a cast does work, however :

doTest((IBarAndBaz*)pFred);

but I wonder how safe and portable this is (I develop for Linux, Mac and Windows), and whether it works in a real-world situation.


Finally, I realize my method can accept a pointer to one of the interfaces and dynamic_cast to the other(s) to enforce the correct parameter type at runtime, but I prefer a compile-time solution.

like image 223
ggambett Avatar asked Jan 18 '10 17:01

ggambett


3 Answers

Consider using tested solutions first - Boost.TypeTraits to the rescue:

template<class T>
void takeFooAndBar(const T& t) {
    BOOST_STATIC_ASSERT(
           boost::is_base_of<IFoo, T>::value 
        && boost::is_base_of<IBar, T>::value);
    /* ... */
}
like image 184
Georg Fritzsche Avatar answered Oct 27 '22 01:10

Georg Fritzsche


To do this in OO style, you need virtual inheritance to ensure that Fred only ends up with one copy of IBar:

class IFooAndBar : public IFoo, public virtual IBar {};
class IBarAndQuux : public virtual IBar, public IQuux {};

class Fred : public IFooAndBar, public IBarAndQuux {};

Fred fred;
fred.bar(); // unambiguous due to virtual inheritence

As others have said, you can do something similar to your second attempt using templates to get static polymorphism.

The cast you were trying isn't possible, as an instance of Fred isn't an instance of IBarAndBaz. The forced cast compiles because most forced casts will compile, whether or not the conversion is safe, but in this case it will give undefined behaviour.

Edit: Alternatively, if you don't want to use templates and don't like the combinatorical explosion of defining all the possible groups of interfaces, you could define the functions to take each interface as a separate parameter:

void doTest(IBar *bar, IBaz *baz)
{
    bar->bar();
    baz->baz();
}

class Fred : public IBar, public IBaz {};

Fred fred;
doTest(&fred,&fred);
like image 35
Mike Seymour Avatar answered Oct 26 '22 23:10

Mike Seymour


You can achieve the effect using template metaprogramming:

tempate<class C>
void doTest( C* pObj )
{
  pObj->bar();
  pObj->baz();
}

will behave correctly for classes that supply bar() and baz(), and fail to compile for any other classes.

like image 1
moonshadow Avatar answered Oct 27 '22 01:10

moonshadow