Как задать соответствие числа и типа данных C++?

От внешнего источника в двоичном виде приходят данные, как можно в зависимости от кода типа данных получать переменную определёного типа, возможно ли это сделать более обобщённо без множества условий? Ниже привожу пример текущей реализации.

// Классы, реализующие обработку сообщений
class MsgType1;
class MsgType2;
class MsgType3;
class MsgType4;

enum class MsgCode {
  Type1,
  Type2,
  Type3,
  Type4
};

void data_reseive(int msg_type, void* msg_data) {
  MsgCode c = static_cast<MsgCode>(msg_type);
  switch (c) {
  case MsgCode::Type1:
  {
    MsgType1 msg;
    msg.process(msg_data);
    break;
  }
  case MsgCode::Type2:
  {
    MsgType2 msg;
    msg.process(msg_data);
    break;
  }
  case MsgCode::Type3:
  {
    MsgType3 msg;
    msg.process(msg_data);
    break;
  }
  case MsgCode::Type4:
  {
    MsgType4 msg;
    msg.process(msg_data);
    break;
  }
  }
}

// Пример с использованием полиморфизма

class MsgTypeBase {
  public:
  virtual void process(void* data) = 0;
  virtual bool operator==(MsgCode code) = 0;
};

template<MsgCode C>
class MsgTypeContBase: MsgTypeBase {
  public:
  bool operator==(MsgCode code) {
    return static_cast<int>(C) == static_cast<int>(code);
  }
};

class MsgTypeCont1: public MsgTypeBase<MsgCode::Type1> {
  public:
  void process(void* data) {
    MsgType1 msg;
    msg.process(data);
  }
};

class MsgTypeCont2: public MsgTypeBase<MsgCode::Type2> {
  public:
  void process(void* data) {
    MsgType2 msg;
    msg.process(msg_data);
  }
};

class MsgTypeCont3: public MsgTypeBase<MsgCode::Type3> {
  public:
  void process(void* data) {
    MsgType3 msg;
    msg.process(msg_data);
  }
};

class MsgTypeCont4: public MsgTypeBase<MsgCode::Type4> {
  public:
  void process(void* data) {
    MsgType4 msg;
    msg.process(msg_data);
  }
};

std::vector<MsgTypeBase*> m_list;

void init() {
  m_list.push_back(new MsgTypeCont1());
  m_list.push_back(new MsgTypeCont2());
  m_list.push_back(new MsgTypeCont3());
  m_list.push_back(new MsgTypeCont4());
}

void data_reseive(int msg_type, void* msg_data) {
  MsgCode c = static_cast<MsgCode>(msg_type);
  for (auto m: m_list) {
    if (*m == c) {
      m->process(msg_data);
      return;
    }
  }
}


Ответы (4 шт):

Автор решения: Глеб Труфанов

Так сделать не получится, поскольку Ваш тип данных MsgType зависит строго от того, какие данные были получены На крайняк можно написать какой-нибудь класс, инкапсилурующий внутри себя все эти проверки и скрывающий реализацию выведения типа MsgType, но реализация, которая у Вас, на самом деле не является костыльной, можно и так оставить.

→ Ссылка
Автор решения: user7860670

Если идентификаторы идут подряд, то можно сделать диспечеризацию по списку типов, если нет (или если их слишком много для перебора) - то по аналогии через таблицу соответствия:

#include <type_traits>
#include <tuple>
#include <utility>

using
t_MsgCode = int;

template
<
    typename x_Msg
>
void
Process
(
    void * const data
);

template
<
    typename x_MsgsTuple
,   typename x_IdsSequence = void
>
struct
t_Dispatcher;

template
<
    typename... x_Msgs
,   t_MsgCode... x_indexes
>
struct
t_Dispatcher<::std::tuple<x_Msgs...>, ::std::integer_sequence<t_MsgCode, x_indexes...>> final
{
    static void
    Dispatch
    (
        t_MsgCode const code
    ,   void * const p_data
    )
    {
        (((code == x_indexes) ? Process<x_Msgs>(p_data) : void()), ...);
    }
};

template
<
    typename... x_Msgs
>
struct
t_Dispatcher<::std::tuple<x_Msgs...>, void> final
{
    static void
    Dispatch
    (
        t_MsgCode const code
    ,   void * const p_data
    )
    {
        using
        t_IdsSequence = ::std::make_integer_sequence
        <
            t_MsgCode
        ,   static_cast<t_MsgCode>(sizeof...(x_Msgs))
        >;

        t_Dispatcher<::std::tuple<x_Msgs...>, t_IdsSequence>::Dispatch(code, p_data);
    }
};

struct
t_FirstMsg {};

struct
t_SecondMsg {};

struct
t_ThirdMsg {};

using
t_Msgs = ::std::tuple<t_FirstMsg, t_SecondMsg, t_ThirdMsg>;

void
Receive_Data
(
    t_MsgCode const code
,   void * const p_data
)
{
    t_Dispatcher<t_Msgs>::Dispatch(code, p_data);
}

online compiler

→ Ссылка
Автор решения: KoVadim

Написал свой вариант.

// Функции, реализующие обработку сообщений
// имена могут быть любые
void process_msg1(void* data) {}
void process_msg2(void* data) {}
void process_msg3(void* data) {}
void process_msg4(void* data) {}

// сигнатура осталась старой
void data_reseive(int msg_type, void* msg_data) {
    // эта мапа помечена static, что бы один раз инициализировать
    static std::map<int, void(*)(void* data)> trans = 
    {
      // привязываем идекс-обработчик
      {1, &process_msg1},
      {2, &process_msg2},
      {3, &process_msg3},
      {4, &process_msg4}
    };

  auto f = trans.find(msg_type);
  if (f == trans.end()) {
      // ой, ничего не найдено
      return;
  }
  // собственно вызываем обработку.
  f->second(msg_data);
}

Если индексы идет подряд, можно использовать массив. Это даст чуточку ускорения. Мапу с функции можно при желании вынести. А вообще при желании можно там вставить лямбды, но это на любителя

→ Ссылка
Автор решения: AlexGlebe

Я в Си делал виртуальную таблицу. Создал массив указателей на функции. И вызываю по коду. Для вашего примера примерно так:

enum class MsgCode {
  Type1,
  Type2,
  Type3,
  Type4
};

typedef void (*functype)(void*);

extern functype virtTable [ ] ;

void data_reseive(int msg_type, void* msg_data) {
  functype f = virtTable [ msg_type ] ;
  f ( msg_data ) ; }

Для каждого типа отдельная функция :

void virtfunc1 ( void * msg_data ) {
  MsgType1 msg;
  msg . process( msg_data ); }

Адреса функций надо будет где-то проставить :

functype virtTable [ ]  = { virtfunc1 , virtfunc2 ,
  virtfunc3 , virtfunc4 } ;

KoVadim примерно похожее показал. Но у меня Си стиль.

→ Ссылка