Вопрос

Часто я сталкиваюсь с проблемой отображения пространства параметров одного API на пространство параметров другого.Часто я вижу , как это решается с помощью nested вложенный вложенный ...операторы переключения.

И мне было интересно, найдется ли случайно библиотека или методика, которая позволит вам "объявлять" отображение вместо "программировать" его.

Тривиальный пример состоял бы в объединении значений двух перечислений в одно:

namespace sourceAPI {
  struct A { typedef e { A1, A2, A3 } };
  struct B { typedef e { B1, B2 } };
}

namespace targetAPI {
  struct AB { typedef e { A1B1, A1B2, A2B1, A2B2, A3B1, A3B2 } };
}

В котором отображение часто выполняется следующим образом

switch( a ){
  case( A::A1 ): switch( b ) {
     case( B::B1 ): return A1B1;
     case( B::B2 ): return A1B2;
     ....
}

И это отображение по-прежнему нуждается в переключении "назад".

Но я бы предпочел что-нибудь "плотное" вроде

declare( source( A::A1, B::B1 ), target( AB::A1B1 ) );
declare( source( A::A1, B::B2 ), target( AB::A1B2 ) );
....

Кто-нибудь видел такую технику, фреймворк или библиотеку?

Это было полезно?

Решение

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

Это сопряжено с небольшими накладными расходами во время выполнения (как правило, примерно такими же, какие вы получили бы, используя пару std::maps для этой цели, что не так уж много).

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

Другие советы

Использование таблично-ориентированного подхода - это нормально - это эквивалентно.

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

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

В моей собственной работе я предпочитаю использовать подобный шаблон:

TargetAPI ToTargetAPI(SourceAPI source)
{
    // ...
}

Там, где что-то может гнездиться, я вызываю другой метод ToXXXX.

Для перечислений мои методы ToXXXX представляют собой либо одиночный переключатель, либо поиск по таблице (или, в некоторых случаях, преобразование выражения), и я проверяю диапазоны ввода с помощью кода, который выдает (будь то проверка границ или инструкция по умолчанию в switch).

Для меня механизм перевода с одного типа на другой менее важен, чем инженерия, которая предотвращает возникновение ошибок при изменении API из-за жесткого и быстрого сбоя.Подумайте об этом с такой точки зрения:будете ли вы терять больше времени, вводя полный оператор switch (вложенный или нет) с проверкой ошибок или отслеживанием ошибки из перечисления, которое находится вне диапазона и не было проверено?

Во многих случаях вы можете добиться этого с помощью простых таблиц подстановки.Поскольку перечисленные типы могут быть приведены к целочисленным значениям, вы можете использовать их в качестве индекса в массиве перечисленных типов другого типа, следовательно, выполняя быстрое и простое преобразование.У этого есть приятный побочный эффект - это делается настолько быстро, насколько это в человеческих силах, чтобы делать подобные вещи.В зависимости от вашего использования таблицы подстановки могут стать довольно большими (но тогда, если вы создаете оператор switch с одним регистром для каждого перечисления, это будет еще больше).Кроме того, если вам нужно двунаправленное преобразование, то вам придется создать 2 таблицы подстановки, по одной для каждого направления.

Кроме того, имейте в виду, что многие компиляторы могут оптимизировать перечисленные типы вплоть до минимального типа данных, необходимого для хранения каждого значения.Есть способы обойти это (часто используется флаг компилятора, или вы можете просто объявить "фиктивное" значение чего-то вроде 0xffffffff, чтобы принудительно выполнить 32-разрядное перечисление), но это стоит отметить.

Если у вас есть нецелочисленные значения, вы можете использовать maps.STL включает в себя несколько разновидностей, и, как кто-то еще упоминал, у boost есть хорошая разновидность, которая является двунаправленной.

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

enum A::e a;
enum B::e b;

// ... set values of a and b.

AB::e result = (b << 2) | a;

В данном конкретном случае, поскольку A имеет ровно три элемента, результатом является даже непрерывный диапазон значений.Когда число членов (всего перечисления, кроме одного) на единицу меньше, чем в степени двойки, результат, тем не менее, будет несмежным.

Я почти уверен, что ваш вопрос действительно должен быть более общим, чем просто поиск способа справиться с этой конкретной проблемой.На самом деле, я подозреваю, что этот пример чисто гипотетический.К сожалению, трудно догадаться, какие другие типы проблем могут вас волновать.Безусловно, существует довольно много примеров декларативного программирования на C ++.Приведу пару очевидных примеров: практически все, что использует Boost Spirit или Boost Xpressive, в конечном итоге выполняет хотя бы некоторое декларативное программирование.К лучшему это или к худшему, однако, оба они посвящены схожим проблемам, которые, оказывается, совсем немного отличаются от тех, которые вас, кажется, волнуют.

Std::map с boost::tuple в качестве ключа.
Если вы не возражаете использовать "make_tuple" в своем объявлении, то у вас уже есть переменные ключевые элементы в вашем объявлении бесплатно.Вам нужно "make_tuple", чтобы выполнить фактическое преобразование.

Редактировать:
Все действительно усложняется, когда требуются диапазоны или подстановочные знаки

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top