5

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.

Luke Givens
  • 829
  • 1
  • 11
  • 15
  • 1
    There are some ways to do this, but you're better off redesigning your program to avoid the problem altogether (rather than spamming things like `dynamic_cast` and `if(x == nullptr)` everywhere) – OMGtechy Jul 26 '14 at 15:00
  • As for how to redesign, it depends what you want your program to do! – OMGtechy Jul 26 '14 at 15:01
  • You can also use [type erasure](http://www.artima.com/cppsource/type_erasure.html) for this. (If I have time later I will try to work up an answer...) – Nemo Jul 26 '14 at 15:32

3 Answers3

1

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();
}
Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Inheritance from designated base classes is not actually needed; see my answer. – Nemo Jul 26 '14 at 20:42
  • I think this is pretty elegant. To help the header, I have provided the implementation for the function any. Hope it is okay. The reader can implement all – Jens Munk Jul 27 '14 at 12:04
  • @Nemo: Your technique requires defining a new `FlyingSwimmingThing` every time, and doesn't allow access to the original `Flying*` or `Swimming*`. It's definitely suboptimal. – Puppy Jul 27 '14 at 12:08
  • (a) The whole point of the question is to avoid creating a common base class. (b) Not allowing access to the original `Flying*` and `Swimming*` objects is called "encapusulation" and is one of the _features_ of type erasure. This is covered in the article I linked in my answer; did you bother to read it? – Nemo Jul 31 '14 at 16:51
  • In fact, you appear not to understand type erasure at all. In my example, there are no `Flying` or `Swimming` base classes. The type-erasing class will contain _any_ class that has `fly` and `swim` member functions, including classes you create in the future, without modification. It turns a "concept" (like "can fly and can swim") into an object that can wrap any other object satisfying that concept. Which is precisely what this question is asking for. – Nemo Jul 31 '14 at 17:00
  • @Nemo: It is not a feature if the OP doesn't want it. You can't randomly encapsulate things. You can only encapsulate implementation details. If needing the source Flying* is part of the OP's interface requirements, which the question implies it is, you just encapsulated what he actually needs. And secondly, I did not create a common base class. You did. The OP does not require "Any object that can fly and swim". He requires "Any object that derives from both Flying and Swimming". This is covered in the question you're answering; did you bother to read it? – Puppy Jul 31 '14 at 17:40
  • (1) It is not a bug if the OP does not forbid it, and encapsulation is usually desirable. Your solution breaks encapsulation by forcing you to know the type of object at the call site; you might as well just use `union { Flying *f; Swimming *s; }`. (2) Your `AnyBase` looks like a common base class to me. (3) _He requires "Any object that derives from both Flying and Swimming"_: What are you talking about? His example `struct A` and `struct B` do not inherit from any common classes at all, and he specifically said he does not want them to. That is, I read the question fine; did you? – Nemo Jul 31 '14 at 20:27
0

You are asking for something which doesn't make sense in general, that's why there is no easy way to do it.

You are asking to be able to store heterogeneus objects in a collection, with interfaces that are even different.

How are you going to iterate over the collections without knowing the type? You are restricted to the least specific or forced to do dynamic_cast pointers and cross fingers.

class Entity { }

class SwimmingEntity : public Entity {
  virtual void swim() = 0;
}

class FlyingEntity : public Entity {
  virtual void fly() = 0;
}

class Fish : public SwimmingEntity {
  void swim() override { }
}

class Bird : public FlyingEntity {
  void fly() override { }
}

std:vector<Entity*> entities;

This is legal but doesn't give you any information to the capabilities of the runtime Entity instance. It won't lead anywhere unless you work them out with dynamic_cast and rtti (or manual rtti) so where's the advantage?

Jack
  • 131,802
  • 30
  • 241
  • 343
  • He doesn't want to do it with different interfaces; he just wants to compose two interfaces. – Puppy Jul 26 '14 at 15:04
  • @Puppy: He actually seems to want to compose optional interfaces, because composing two interfaces is perfectly feasible with multiple inheritance. – Jack Jul 26 '14 at 15:05
  • That doesn't produce the same result, because you have to manually define a new class every time and then every derived class has to know about that new class ahead of time. You can't just compose two interfaces and get a new interface that is the composition of those two. You can only define a completely new interface, which happens to include two existing ones. – Puppy Jul 26 '14 at 15:06
  • @Puppy: You can't dynamically, of course, you can just define a new interface which is the composition of the two at compile time, and that's it. There is no other way, but I don't think the OP is looking for another way, I thought he was looking for storing objects which could either implement one interface, either another, either both. – Jack Jul 26 '14 at 15:11
  • No, he wants to store any object that implements two interfaces independently. – Puppy Jul 26 '14 at 15:12
  • They must implement the two interfaces, and i need to call both in functions. – Luke Givens Jul 26 '14 at 15:14
  • @Puppy: then I don't get the requirement, if both interfaces MUST be implemented by the object there is no need to decouple them. And if there is a need, that's related to another third party client of these classes. So all the independent interfaces could be inherited by a single interface which is used to store elements in the collection while still being decoupled for other clients. – Jack Jul 26 '14 at 15:17
  • Somewhere else i could need a collection of just swimming objects. Without duplicating the interface – Luke Givens Jul 26 '14 at 15:26
  • @Jack: It's not decoupled at all because the definer of the class has to know about all of the composing interfaces, and inherit from them all, in advance. Probably involving a bunch of virtual inheritance too. – Puppy Jul 26 '14 at 15:32
  • A common (external) base class is not actually needed; see my answer. – Nemo Jul 26 '14 at 20:42
0

This is pretty much a textbook example calling for type erasure.

The idea is to define an internal abstract (pure virtual) interface class that captures the common behavior(s) you want, then to use a templated constructor to create a proxy object derived from that interface:

#include <iostream>
#include <vector>
#include <memory>

using std::cout;

struct Bird {
  void fly() { cout << "Bird flies\n"; }
  void swim(){ cout << "Bird swims\n"; }
};

struct Pig {
  void fly() { cout << "Pig flies!\n"; }
  void swim() { cout << "Pig swims\n"; }
};

struct FlyingSwimmingThing {
  // Pure virtual interface that knows how to fly() and how to swim(),
  // but does not depend on type of underlying object.
  struct InternalInterface {
    virtual void fly() = 0;
    virtual void swim() = 0;

    virtual ~InternalInterface() { }
  };

  // Proxy inherits from interface; forwards to underlying object.
  // Template class allows proxy type to depend on object type.
  template<typename T>
  struct InternalImplementation : public InternalInterface {
    InternalImplementation(T &obj) : obj_(obj) { }
    void fly() { obj_.fly(); }
    void swim() { obj_.swim(); }
    virtual ~InternalImplementation() { }
  private:
    T &obj_;
  };

  // Templated constructor
  template<typename T>
  FlyingSwimmingThing(T &obj) : proxy_(new InternalImplementation<T>(obj))
  { }
  // Forward calls to underlying object via virtual interface.                  
  void fly() { proxy_->fly(); }
  void swim() { proxy_->swim(); }

private:
  std::unique_ptr<InternalInterface> proxy_;
};

int main(int argc, char *argv[])
{
  Bird a;
  Pig b;

  std::vector<FlyingSwimmingThing> objects;
  objects.push_back(FlyingSwimmingThing(a));
  objects.push_back(FlyingSwimmingThing(b));

  objects[0].fly();
  objects[1].fly();
  objects[0].swim();
  objects[1].swim();
}

The same trick is used for the deleter in a shared_ptr and for std::function. The latter is arguably the poster child for the technique.

You will always find a call to "new" in there somewhere. Also, if you want your wrapper class to hold a copy of the underlying object rather than a pointer, you will find you need a clone() function in the abstract interface class (whose implementation will also call new). So these things can get very non-performant very easily, depending on what you are doing...

[Update]

Just to make my assumptions clear, since some people appear not to have read the question...

You have multiple classes implementing fly() and swim() functions, but that is all that the classes have in common; they do not inherit from any common interface classes.

The goal is to have a wrapper object that can store a pointer to any one of those classes, and through which you can invoke the fly() and swim() functions without knowing the wrapped type at the call site. (Take the time to read the question to see examples; e.g. search for dostuff.) This property is called "encapsulation"; that is, the wrapper exposes the fly() and swim() interfaces directly and it can hide any properties of the wrapped object that are not relevant.

Finally, it should be possible to create a new otherwise-unrelated class with its own fly() and swim() functions and have the wrapper hold a pointer to that class (a) without modifying the wrapper class and (b) without touching any call to fly() or swim() via the wrapper.

These are, as I said, textbook features of type erasure. I did not invent the idiom, but I do recognize when it is called for.

Community
  • 1
  • 1
Nemo
  • 70,042
  • 10
  • 116
  • 153
  • The primary downside of this is that `FlyingSwimmingThing` cannot be re-used to create `WalkingSwimmingThing` and `FlyingWalkingThing`. – Puppy Jul 27 '14 at 12:09
  • @Pubby: That is true, but it has nothing to do with what the question asked. I have updated my answer to repeat what the question asked. – Nemo Jul 31 '14 at 20:47