TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #1127 3.77K
Мапим значения на типы. Ч2
#опытным

Напомню проблему:

enum class DataType: uint8_t { 
kInt32,
kDouble,
kUint64
};

template<typename T>
T create_from_buffer(const void* buffer) {
static_assert(std::is_trivially_copyable_v<T>,
"Type T must be trivially copyable");

T obj;
std::memcpy(&obj, buffer, sizeof(T));
return obj;
}


Как на основе DataType создать объект соответствующего типа?

Compile-time мапа из массива, где по индексу подкапотного числа, стоящего за каждым перечислителем, позволяет получить соответствующий нужный тип и выбрать правильный обработчик. И все это за О(1) с тратами на сдвиг элемента в массиве

Если же перечислителям явно присвоены числа, то этот номер не прокатит и надо думать дальше.

Без шаблонной магии нам не обойтись, ведь мы хотим только добавлять маппинги, но не менять самого кода обработчиков и логику их выбора.

И давайте заодно попробуем уйти от полной специализации шаблонов, как было в предыдущей части.

Сделаем структуру аля ноду маппинга:

template <DataType D, typename T>
struct Node { static constexpr DataType key = D; using type = T; };


В структуре хранится конкретный перечислитель, получаемый из шаблонного параметра, и соответствующий ему тип.

И в качестве пака шаблонных параметров нашей шаблонной функции мы будем передавать список нод Node<DataType::kInt32, int32_t>, Node<DataType::kDouble, double>, Node<DataType::kUint64, uint64_t>.

В функции мы должны сделать примерно следующее. Перебираем рантайм значение enum'а на соответствие значению Key из нод пака параметров. Перебираем, пока не найдем нужный и после используем правильную ноду для получения замаппленого типа.

И вот этот перебор можно делать через fold expression и короткозамкнутый оператор ||. Как только условие истинно, вычисления прекращаются.

template <typename... Es>
DataVariant ConvertImpl(const void* buffer, DataType type) {
DataVariant result;
bool found = ((Es::key == type
? (result = create_from_buffer<typename Es::type>(buffer), true)
: false) || ...);
if (!found) throw std::runtime_error("Unknown type");
return result;
}

DataVariant Convert(const void* buffer, DataType type) {
return ConvertImpl<
Node<DataType::kInt32, int32_t>,
Node<DataType::kDouble, double>,
Node<DataType::kUint64, uint64_t>
>(buffer, type);
}


Также тут используется фишка оператора запятой, что результатом выражения является только самый крайний справа операнд.

Теперь вместо поиска элемента в массиве по индексу мы занимаемся линейным проходом выполнения логического ИЛИ для типов.

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

Код кстати по размеру уже не сильно больше изначального свитча.

Вот примерчик с рабочим кодом, можете поиграться.

Use tricks. Stay cool.

#template #cpp17
  • 🔥 14
  • ❤ 7
  • 👍 7
  • ⚡ 1
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →