По декларативному программированию в C ++ - PullRequest
4 голосов
/ 07 декабря 2009

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

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

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

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 ) );
....

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

Ответы [ 5 ]

3 голосов
/ 07 декабря 2009

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

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

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

1 голос
/ 07 декабря 2009

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

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

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

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

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

Где что-то может быть вложено, я вызываю другой метод ToXXXX.

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

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

1 голос
/ 07 декабря 2009

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

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

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

0 голосов
/ 07 декабря 2009

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

Edit:
Вещи усложняются, когда нужны диапазоны или подстановочные знаки

0 голосов
/ 07 декабря 2009

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

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, заканчивают тем, что делали по крайней мере некоторое декларативное программирование. Однако, к лучшему или худшему, оба они посвящены схожим проблемам, которые, как оказалось, немного отличаются от тех, о которых вы, похоже, беспокоитесь.

...