1

Let's suppose we have the following objects:

struct A{
    static const int ID = 1;
};

struct B{
    static const int ID = 2;
};

struct C{
    static const int ID = 3;
};

struct D{
    static const int ID = 4;
};

struct Collection{
    std::vector<A> o1;
    std::vector<B> o2;
    std::vector<C> o3;
    std::vector<D> o4;
};

Collection collection;

What I want to do is getting references to some of the vectors of the Collection. There should be three different ways to retrieve these:

  1. By type of vector, e.g. collection.get<A>();

  2. By ID on compile time which is defined by every object that is held by a vector, e.g. collection.get<4>();

  3. By ID on runtime, e.g. collection.get(id);

Case 1 is easy as it can be transformed into case 2 with T::ID. Case 2 can be implemented with template specialization (although it looks ugly if I'd have lots of objects). Case 3 is making a lot of trouble. Without some giant if or switch statements it seams to be nearly impossible, let alone deducing the return type.

My questions are:

  • Is there a way to make case 2 more elegant?
  • How should I implement case 3?
Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
Overblade
  • 656
  • 2
  • 7
  • 25
  • Will there always only be `A, B, C, D`? – Micha Wiedenmann Jul 21 '17 at 13:09
  • Probably no, there will be at least 30. – Overblade Jul 21 '17 at 13:09
  • You have a runtime value. You don't know what it is. You need to operate on a different type depending on that value, meaning a completely different code path (even if it's a different instantiation of the same function template). That means a run-time branch. So, you either have a `switch` or a `map` - those are pretty much your choices. – Useless Jul 21 '17 at 13:16
  • But how would I return the `vector` from a `switch` statement? The compiler moans that the return type can't be deduced. – Overblade Jul 21 '17 at 13:19
  • You can't - the switch needs to dispatch into a type-specific codepath. Think of it like a Visitor. – Useless Jul 21 '17 at 13:28

1 Answers1

1

There are things you can do, and things you can never do. What you're asking for is not that simple, and needs hard work.

1 and 2 are difficult, but achievable. First, create a static map of all type-IDs, then use the static result to choose the type.

Now 3... well... sorry to tell you it's impossible! To provide a counter-example that proves what I'm saying: You can never create a function that chooses the return type at runtime. But...

There's something you can do, which is basically what the LLVM people did to get rid of dynamic_cast, but it's too much code.

First, case a base abstract class, and make all these classes derive from it, and make it contain the ID:

struct Base{
    virtual int getClassID() = 0;
    static Base* CreateObject(int ID) //consider using shared_ptr here
        {
        switch(ID) {
        case A::ID:
            return new A;
        }            
        case B::ID:
            return new B;
        default:
            return nullptr;
        }
        //...
    }
}

struct A : public Base {
    static const int ID = 1;
    int getClassID() override {
        return ID;
    }
};

struct B : public Base {
    static const int ID = 2;
    int getClassID() override {
        return ID;
    }
};

struct C : public Base {
    static const int ID = 3;
    int getClassID() override {
        return ID;
    }
};

struct D : public Base {
    static const int ID = 4;
    int getClassID() override {
        return ID;
    }
};

Now, to instantiate a new class, you use you Base class.

Base* c = Base::CreateObject(3); //object of C

Now, finally, you create your magic cast operator:

template <typename T>
T* MyMagicCast(Base* obj)
{
    if(obj->getClassID() ISVALID) //Then use the ID to make sure that T matches the ID
    {
        return static_cast<T*>(obj);
    }
    else
    {
        return nullptr;
    }
}

And this will do the runtime stuff for you. With this method, you have the advantage that all types operation can be done by calling the base + you're not using any RTTI. Now ONLY if you need something specific to one of the types you have (A,B,C,D), you use MyMagicCast() to cast to their type.

PS: The code is off the top of my head, so there might be slight typos/errors.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189