0

I'd like to simplify the code I write in my application that handles mutiple data structure types but with a common header. Given something like this:

enum class MyType {
  Foo = 100,
  Bar = 200,
};

struct Hdr {
  MyType type;
};

struct Foo {
  Hdr hdr;
  int x;
  int y;
  int z;
};

struct Bar {
  Hdr hdr;
  double value;
  double ratio;
};

void process(const Foo *ptr)
{
  // process Foo here
}

void process(const Bar *ptr)
{
  // process Bar here
}

extern void *getData();

int main()
{
  const void *pv = getData();
  auto pHdr = static_cast<const Hdr *>(pv);
  switch (pHdr->type) {
    case MyType::Foo: process(static_cast<const Foo *>(pv)); break;
    case MyType::Bar: process(static_cast<const Bar *>(pv)); break;
    default: throw "Unknown";
  }
  return 0;
}

Ideally I'd like to replace the switch statement above with something like:

process(multi_cast<pHdr->type>(pv);

I'm perfectly okay with having to write statements like this to get it to work:

template<MyType::Foo>
const Foo *multi_cast(void *p)
{
  return static_cast<const Foo *>(p);
}

template<MyType::Bar>
const Bar *multi_cast(void *p)
{
  return static_cast<const Bar *>(p);
}

But I cannot write a template where the template parameter is a enum (or an int for that matter) Have I just looked at this for so long that I cannot see an answer? Or is there just no other way to do it?

3
  • 3
    You cannot use a runtime value to choose a template specialization. You will, one way or another, need to check the value at runtime and decide on which function to call. Commented May 25, 2017 at 20:34
  • Why not use a virtual function? Commented May 25, 2017 at 20:49
  • You cannot get rid of the switch at the heart of the read loop. This is because you only know which type to cast this into at the run time. Commented May 25, 2017 at 20:53

3 Answers 3

3

There is just no other way to do it.

As the comments have pointed out, since the type is stored in the header at run-time, you have to have some kind of run-time lookup; no amount of templates or overload resolution can help you since all of that is at compile-time.

You can abstract the lookup as much as you want, but you can only replace the switch statement with another type of lookup, and you can only decrease performance the further you get away from a simple switch/lookup table.

For example, you could start with something like this and go nuts:

#include <iostream>
#include <cassert>

enum class Type {
    FOO,
    BAR,
    NUM_
};

struct Header {
    Header(Type t)
        : type(t)
    {}

    Type type;
};

struct Foo {
    Foo(int x, int y, int z)
        : header(Type::FOO), x(x), y(y), z(z)
    {}

    Header header;
    int x;
    int y;
    int z;
};

struct Bar {
    Bar(double value, double ratio)
        : header(Type::BAR), value(value), ratio(ratio)
    {}

    Header header;
    double value;
    double ratio;
};

static inline void process(Foo*) {
    printf("processing foo...\n");
}

static inline void process(Bar*) {
    printf("processing bar...\n");
}

using ProcessFunc = void(*)(void*);
static ProcessFunc typeProcessors[(size_t)Type::NUM_] = {
    [](void* p) { process((Foo*)p); },
    [](void* p) { process((Bar*)p); },
};

static void process(void* p) {
    Type t = ((Header*)p)->type;
    assert((size_t)t < (size_t)Type::NUM_ && "Invalid Type.");

    typeProcessors[(size_t)t](p);
}

static void* get_foo()
{
    static Foo foo(0, 0, 0);
    return &foo;
}

static void* get_bar()
{
    static Bar bar(0.0, 0.0);
    return &bar;
}

int main() {
    Foo foo(0, 0, 0);
    Bar bar(0.0, 0.0);

    process(&foo);
    process(&bar);

    process(get_foo());
    process(get_bar());

    return 0;
}

but then you're only getting cute and most likely slower. You might as well just put the switch in process(void*)

If you aren't serializing your data(doubtful), are mostly processing one type at a time, and want an OO solution(I wouldn't), you could return a base type that your types inherit from and add a pure virtual process function like so:

struct Type { 
    virtual void process() = 0;
    virtual ~Type() {} 
};

struct Foo : Type {
    int x = 0;
    int y = 0;
    int z = 0;

    virtual void process() override {
        printf("processing foo...\n");
    }
};

struct Bar : Type {
    double value = 0.0;
    double ratio = 0.0;

    virtual void process() override {
        printf("processing bar...\n");
    }
};

static Type* get_foo() {
    static Foo foo;
    return &foo;
}

static Type* get_bar() {
    static Bar bar;
    return &bar;
}

int main() {
    Foo foo;
    Bar bar;

    foo.process();
    bar.process();

    get_foo()->process();
    get_bar()->process();

    return 0;
}

I would stick with the switch, but I would keep the values of Type::FOO and Type::BAR the default 0 and 1. If you mess with the values too much, the compiler might decide to implement the switch as a bunch of branches as opposed to a lookup table.

Sign up to request clarification or add additional context in comments.

Comments

-1

You have two issues:

  1. Converting a runtime value (your "type") into a compile time determined type (with associated behavior).
  2. "Unifying" the possible different types to a single (statically at compile time known) type.

Point 2 is what inheritance together with virtual member functions are for:

struct Thing {
  virtual void doStuff() const = 0;
  virtual ~Thing() {}
};
struct AThing : Thing {
  void doStuff() const override { std::cout << "A"; }
};
struct BThing : Thing {
  void doStuff() const override { std::cout << "B"; }
};

Point 1 is usually tackled by creating some kind of "factory" mechanism, and then dispatching based on the runtime value to one of those factories. First, the factories:

Thing * factoryA() { return new AThing(); }
Thing * factoryB() { return new BThing(); }
Thing * factory_failure() { throw 42; }

The "dispatching" (or "choosing the right factory") can be done in different ways, one of those being your switch statement (fast, but clumsy), linear search through some container/array (easy, slow) or by lookup in a map (logarithmic - or constant for hashing based maps).

I chose a (ordered) map, but instead of using std::map (or std::unordered_map) I use a (sorted!) std::array to avoid dynamic memory allocation:

// Our "map" is nothing more but an array of key value pairs.
template <
  typename Key,
  typename Value,
  std::size_t Size>
using cmap = std::array<std::pair<Key,Value>, Size>;


// Long type names make code hard to read.
template <
  typename First,
  typename... Rest>
using cmap_from =
  cmap<typename First::first_type,
       typename First::second_type,
       sizeof...(Rest) + 1u>;


// Helper function to avoid us having to specify the size
template <
  typename First,
  typename... Rest>
cmap_from<First, Rest...> make_cmap(First && first,
                                    Rest && ... rest) {
  return {std::forward<First>(first), std::forward<Rest>(rest)...};
}

Using std::lower_bound I perform a binary search on this sorted array (ehm "map"):

// Binary search for lower bound, check for equality
template <
  typename Key,
  typename Value,
  std::size_t Size>
Value get_from(cmap<Key,Value,Size> const & map,
               Key const & key,
               Value alternative) {
  assert(std::is_sorted(std::begin(map), std::end(map),
                        [](auto const & lhs, auto const & rhs) {
                          return lhs.first < rhs.first; }));
  auto const lower = std::lower_bound(std::begin(map), std::end(map),
                                      key,
                                      [](auto const & pair, auto k) {
                                        return pair.first < k; });
  if (lower->first == key) {
    return lower->second;
  } else {
    // could also throw or whatever other failure mode
    return alternative;
  }
}

So that, finally, I can use a static const map to get a factory given some runtime value "type" (or choice, as I named it):

int main() {
  int const choices[] = {1, 10, 100};
  static auto const map =
    make_cmap(std::make_pair(1, factoryA),
              std::make_pair(10, factoryB));
  try {
    for (int choice : choices) {
      std::cout << "Processing choice " << choice << ": ";
      auto const factory = get_from(map, choice, factory_failure);
      Thing * thing = factory();
      thing->doStuff();
      std::cout << std::endl;
      delete thing;
    }
  } catch (int const & value) {
    std::cout << "Caught a " << value
              << " ... wow this is evil!" << std::endl;
  }
}

(Live on ideone)

The initialization of that "map" could probably made constexpr.

Of course instead of raw pointers (Thing *) you should use managed pointers (like std::unique_ptr). Further, if you don't want to have your processing (doStuff) as member functions, then just make a single "dispatching" (virtual) member function that calls out to a given function object, passing the own instance (this). With a CRTP base class, you don't need to implement that member function for every one of your types.

5 Comments

What is the point of all this map, if in the end you make a virtual function call through the base class pointer thing? I think you want a better example.
@Walter There isn't a point. This is a textbook example of modern C++ disease. This example is slow, unnecessarily complex, and, by consequence, unreadable. Avoid solutions like this at all costs.
@Walter the map maps the runtime enum value to the creation of objects with different (dynamic) type. That's what the question asks for. The virtual member function could be left out in case the objects are "one time use" and don't model some more complex behavior.
There is no point doing that if later a virtual table look-up is made anyway.
@Walter yes there is, since you can't do object creation (calling a constructor) through a virtual function lookup. C++ does not have this feature, so I need to implement it.
-1

You're using something that may be called static (=compile-time) polymorphism. This requires to make such switch statements in order to convert the run-time value pHrd->dtype to one of the compile-time values handles in the case clauses. Something like your

process(multi_cast<pHdr->type>(pv);

is impossible, since pHdr->type is not known at compile time.

If you want to avoid the switch, you can use ordinary dynamic polymorphism and forget about the enum Hdr, but use a abstract base class

struct Base {
  virtual void process()=0;
  virtual ~Base() {}
};

struct Foo : Base { /* ... */ };
struct Bar : Base { /* ... */ };

Base*ptr = getData();
ptr->process();

4 Comments

Did you forget to make the base destructor virtual?
You're missing the IMO most critical issue of the OP: There's an value of an enum which shall determine the type of object to create (and thus by extension the behavior). This is hidden behind getData in your example.
@DanielJour You misunderstand my point: the whole enum --> dynamic object type mapping is unnecessary here, simple polymorphism is sufficient.
@Walter I doubt that: The original code has a function void *getData(); that I assume cannot be changed (could be data from some socket or device). How do you get objects of different dynamic type depending on the first byte (or more generally value)?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.