Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ - Accessing multiple object's interfaces via a single pointer

I need to store a container of pointers to objects. These objects have some common methods/attributes (interface) that I want to enforce (possibly at compile time) and use. Example:

struct A{
    void fly(){}
};

struct B{
    void fly(){}
};

A a;
B b;
std::vector<some *> objects;
objects.push_back(&a);
objects.push_back(&b);

for(auto & el: objects)
    el->fly();

The simpler solution would be A and B inherit a common base class like FlyingClass:

struct FlyingClass{
    void fly(){}
};
struct A: public FlyingClass { ...
struct B: public FlyingClass { ...

and create a

std::vector<FlyingClass *> objects;

This will work and also enforce the fact that I can only add to objects things that can fly (implement FlyingClass).

But what if I need to implement some other common methods/attributes WITHOUT coupling them with the above base class?

Example:

struct A{
    void fly(){}
    void swim(){}
};

struct B{
    void fly(){}
    void swim(){}
};

And i would like to do:

for(auto & el: objects) {
    el->fly();
    ...
    el->swim();
    ...
}

More in general i would be able to call a function passing one of these pointers and access both the common methods/attributes, like:

void dostuff(Element * el){
    el->fly();
    el->swim();
}

I could try to inherit from another interface like:

struct SwimmingClass{
    void swim(){}
};

struct A: public FlyingClass, public SwimmingClass { ...
struct B: public FlyingClass, public SwimmingClass { ...

But then what the container should contain?

std::vector<FlyingClass&&SwimmingClass *> objects;

Sure, i could implement SwimmingFlyingClass, but what if i need RunningClass etc.. This is going to be a nightmare. In other words, how can I implement a pointer to multiple interfaces without coupling them?

Or there is some template way of rethinking the problem? Even run time type information could be acceptable in my application, if there is an elegant and maintainable way of doing this.

like image 274
Luke Givens Avatar asked Nov 10 '22 04:11

Luke Givens


1 Answers

It is possible to do this, in a pretty TMP-heavy way that's a little expensive at runtime. A redesign is favourable so that this is not required. The long and short is that what you want to do isn't possible cleanly without language support, which C++ does not offer.

As for the ugly, shield your eyes from this:

struct AnyBase { virtual ~AnyBase() {} }; // All derived classes inherit from.
template<typename... T> class Limited {
    AnyBase* object;
    template<typename U> Limited(U* p) {
        static_assert(all<is_base_of<T, U>...>::value, "Must derive from all of the interfaces.");
        object = p;
    }        
    template<typename U> U* get() {
        static_assert(any<is_same<U, T>...>::value, "U must be one of the interfaces.");
        return dynamic_cast<U*>(object);
    }
}

Some of this stuff isn't defined as Standard so I'll just run through it. The static_assert on the constructor enforces that U inherits from all of T. I may have U and T the wrong way round, and the definition of all is left to the reader.

The getter simply requires that U is one of the template arguments T.... Then we know in advance that the dynamic_cast will succeed, because we checked the constraint statically.

It's ugly, but it should work. So consider

std::vector<Limited<Flying, Swimming>> objects;
for(auto&& obj : objects) {
    obj.get<Flying>()->fly();
    obj.get<Swimming>()->swim();
}
like image 121
Puppy Avatar answered Nov 15 '22 09:11

Puppy