Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritance of interfaces in C++

I have an object interface and a open ended collection of interfaces that a derived object might want to support.

// An object
class IObject
{
    getAttribute() = 0
}

// A mutable object
class IMutable
{
    setAttribute() = 0
}

// A lockable object 
class ILockable
{
    lock() = 0
}

// A certifiable object 
class ICertifiable
{
    setCertification() = 0
    getCertification() = 0
}

Some derived objects might look like this:

class Object1 : public IObject, public IMutable, public ILockable {}
class Object2 : public IObject, public ILockable, public ICertifiable {}
class Object3 : public IObject {}

Here is my question: Is there a way to write functions that will only take certain combinations of these interfaces? For example:

void doSomething(magic_interface_combiner<IObject, IMutable, ILockable> object);

doSomething( Object1() )  // OK, all interfaces are available.
doSomething( Object2() )  // Compilation Failure, missing IMutable.
doSomething( Object3() )  // Compilation Failure, missing IMutable and ILockable.

The closest thing I've found is boost::mpl::inherit. I've had some limited success but it doesn't do exactly what I need.

For example:

class Object1 : public boost::mpl::inherit<IObject, IMutable, ILockable>::type
class Object2 : public boost::mpl::inherit<IObject, ILockable, ICertifiable>::type
class Object3 : public IObject

void doSomething(boost::mpl::inherit<IObject, ILockable>::type object);

doSomething( Object1() )  // Fails even though Object1 derives from IObject and ILockable.
doSomething( Object2() )  // Fails even though Object2 derives from IObject and ILockable.

I think something similar to boost::mpl::inherit but that would generate an inheritance tree with all possible permutations of the supplied types might work.

I'm also curious about other approaches to solving this problem. Ideally something that does compile time checks as opposed to runtime (i.e. no dynamic_cast).

like image 796
kede Avatar asked Feb 21 '13 16:02

kede


3 Answers

You can write an interface checking class using recursive variadic inheritance:

template<typename... Interfaces>
struct check_interfaces;
template<>
struct check_interfaces<> {
   template<typename T> check_interfaces(T *) {}
};
template<typename Interface, typename... Interfaces>
struct check_interfaces<Interface, Interfaces...>:
public check_interfaces<Interfaces...> {
   template<typename T> check_interfaces(T *t):
      check_interfaces<Interfaces...>(t), i(t) {}
   Interface *i;
   operator Interface *() const { return i; }
};

For example:

struct IObject { virtual int getAttribute() = 0; };
struct IMutable { virtual void setAttribute(int) = 0; };
struct ILockable { virtual void lock() = 0; };

void f(check_interfaces<IObject, IMutable> o) {
   static_cast<IObject *>(o)->getAttribute();
   static_cast<IMutable *>(o)->setAttribute(99);
}

struct MutableObject: IObject, IMutable {
   int getAttribute() { return 0; }
   void setAttribute(int) {}
};

struct LockableObject: IObject, ILockable {
   int getAttribute() { return 0; }
   void lock() {}
};

int main() {
   f(new MutableObject);
   f(new LockableObject);  // fails
}

Note that check_interfaces has a footprint of one pointer per interface checked; this is because it performs type erasure on the declared type of the actual argument.

like image 140
ecatmur Avatar answered Nov 03 '22 01:11

ecatmur


You should use static_assert to check the types within the function:

#include <type_traits>

template< typename T >
void doSomething( const T& t )
{
   static_assert( std::is_base_of<IObject,T>::value, "T does not satisfy IObject" );
   static_assert( std::is_base_of<IMutable,T>::value, "T does not satisfy IMutable" );

   // ...
}

which will give you very nice error messages telling you which interfaces are not satisfied. If you need to overload the function and have a version that is only available for a certain interface combination, you could also use enable_if:

#include <type_traits>

template< typename T, typename... Is >
struct HasInterfaces;

template< typename T >
struct HasInterfaces< T > : std::true_type {};

template< typename T, typename I, typename... Is >
struct HasInterfaces< T, I, Is... >
  : std::integral_constant< bool,
      std::is_base_of< I, T >::value && HasInterfaces< T, Is... >::value > {};

template< typename T >
typename std::enable_if< HasInterfaces< T, IObject, IMutable >::value >::type
doSomething( const T& t )
{
  // ...
}

which will make the function disappear from the overload set when the interface requirements are not met.

like image 27
Daniel Frey Avatar answered Nov 03 '22 01:11

Daniel Frey


A solution using std::enable_if and std::is_base_of:

#include <type_traits>

// An object
struct IObject
{
    virtual void getAttribute() = 0;
};

// A mutable object
struct IMutable
{
    virtual void setAttribute() = 0;
};

// A lockable object 
struct ILockable
{
    virtual void lock() = 0;
};

// A certifiable object 
struct ICertifiable
{
    virtual void setCertification() = 0;
    virtual void getCertification() = 0;
};

struct Object1 : public IObject, public IMutable, public ILockable
{
    void getAttribute() {}
    void setAttribute() {}
    void lock() {}
};

struct Object2 : public IObject, public ILockable, public ICertifiable
{
    void getAttribute() {}
    void lock() {}
    void setCertification() {}
    void getCertification() {}
};

struct Object3 : public IObject
{
    void getAttribute() {}
};

template<typename T>
void doSomething(
    typename std::enable_if<
        std::is_base_of<IObject, T>::value &&
        std::is_base_of<IMutable, T>::value &&
        std::is_base_of<ILockable, T>::value,
        T>::type& obj)
{
}

int main()
{
    Object1 object1;
    Object2 object2;
    Object3 object3;

    doSomething<Object1>(object1);  // Works
    doSomething<Object2>(object2);  // Compilation error
    doSomething<Object3>(object3);  // Compilation error
}
like image 30
Some programmer dude Avatar answered Nov 03 '22 01:11

Some programmer dude