Multiple inheritance of interfaces in C++
Asked Answered
M

5

7

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

Meath answered 21/2, 2013 at 16:56 Comment(5)
Do you mean AND-combinations, or OR-combinations?Vercingetorix
Correct me if I'm wrong, but don't your abstract functions need to be marked virtual as well as being =0?Guyot
I take it use templates instead is the wrong answer?Reggy
In the new C++11 standard, there are lots of new type traits, like for example std::is_base_of. Might be used in combination with std::enable_if.Hager
Why do you need doSomething to get passed so many interfaces at once? Your problems might be hinting a design flaw.Sand
T
3

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.

Tumer answered 21/2, 2013 at 17:16 Comment(2)
Is static_assert available in pre-C++11 language?Monacid
Yes: BOOST_STATIC_ASSERT_MSG(v, msg)Tumer
M
3

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.

Marrin answered 21/2, 2013 at 17:18 Comment(0)
H
2

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
}
Hager answered 21/2, 2013 at 19:22 Comment(0)
V
1

Maybe it is not the most elegant way, since it's done with C++03 syntax

template <typename T, typename TInterface>
void interface_checker(T& t)
{
    TInterface& tIfClassImplementsInterface = static_cast<TInterface&>(t);
}

This is to give you the spirit of the trick. Now in your case:

template <typename T, typename TInterface1, typename TInterface2, typename TInterface3 >
void magic_interface_combiner(T& t)
{
    TInterface1& tIfClassImplementsInterface = static_cast<TInterface1&>(t);
    TInterface2& tIfClassImplementsInterface = static_cast<TInterface2&>(t);
    TInterface3& tIfClassImplementsInterface = static_cast<TInterface3&>(t);
}

I guess it can be done way smarter using C++11 type traits.

Violoncello answered 21/2, 2013 at 17:12 Comment(0)
S
1

Just to give you a small taste of C++11:

Single type without recursion:

template <typename... Ts>
class magic_interface_combiner {
  typedef std::tuple<Ts*...> Tpl;
  Tpl tpl;

  template <typename T, int I>
  T *as_(std::false_type)
  {
    static_assert(I < std::tuple_size<Tpl>::value, "T not found");
    return as_<T, I+1>(std::is_same<T, typename std::tuple_element<I+1, Tpl>::type>{});
  }
  template <typename T, int I>
  T *as_(std::true_type) { return std::get<I>(tpl); }

public:
  template <typename T>
  magic_interface_combiner(T * t) : tpl(static_cast<Ts*>(t)...) {}

  template <typename T> T * as() { return as_<T, 0>(std::false_type{}); }
};

// no template    
void doSomething(magic_interface_combiner<IObject, IMutable, ILockable> object)
{
}

Two types but without recursion:

template <typename T>
class single_interface_combiner {
  T *p;
public:
  single_interface_combiner(T *t) : p(t) {}
  operator T* () { return p; }
};

template <typename... Ts>
struct magic_interface_combiner : single_interface_combiner<Ts>... {
  template <typename T>
  magic_interface_combiner(T* t) : single_interface_combiner<Ts>(t)... {}

  template <typename T>
  T * as() { return *this; }
};
Stilwell answered 21/2, 2013 at 17:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.