Стиль сопоставления с образцом в C ++? - PullRequest
7 голосов
/ 07 февраля 2010

Я люблю сопоставление с шаблоном в стиле Haskell.

У меня есть код C ++, следующий:

ObjectPtr ptr;
if(ptr.isType<Foo>()) { // isType returns a bool
  Ptr<Foo> p = ptr.convertAs<Foo>(); // convertAs returns a Ptr<Foo>
  ......
}
if(ptr.isType<Bar>()) {
  Ptr<Bar> p = ptr.convertAs<Bar>();
  ......
}

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

Спасибо!

Ответы [ 6 ]

7 голосов
/ 07 февраля 2010

Я люблю сопоставление с шаблоном в стиле Haskell.

Тогда напишите свою программу на Haskell.

То, что вы пытаетесь сделать, это переключение типа. Это обычная вещь, которую люди делают, если хотят избежать виртуальных функций. Теперь последние являются краеугольным камнем того, что такое ОО в C ++. Если вы хотите избежать их, почему вы программируете на C ++?


Что касается того, почему это осуждается: представьте, что у вас много такого кода

if(ptr.isType<Foo>()) ...
if(ptr.isType<Bar>()) ... 

размазан по всему вашему коду, а затем кто-то приходит и добавляет Baz к возможным типам, которые может представлять ptr. Теперь вы просматриваете большую кодовую базу, пытаетесь найти все те места, где вы переключили тип, и пытаетесь выяснить, к каким из них нужно добавить Baz.

И, как и у Мерфи, как только вы закончите, придет Foz, который также будет добавлен как тип. (Или, подумав еще раз, если у Мерфи будет свой путь, он закрадется за до , у вас был слишком полный шанс добавить Baz.)

7 голосов
/ 07 февраля 2010

dynamic_cast будет делать то, что вы хотите

struct A {
  virtual ~A() {}
};

struct B : struct  A { ... };
struct C : struct  A { ... };

A * a = new C;

if ( C * c = dynamic_cast<C*>( a ) ) {
   c->someCfunc();
}
else if ( B * b = dynamic_cast<B*>( a ) ) {
   b->someBfunc();
}
else {
   throw "Don't know that type";
}
5 голосов
/ 07 февраля 2010

Попытка смоделировать стиль сопоставления с образцом в C ++ с использованием RTTI является изящной идеей, но она неизбежно имеет недостатки, потому что есть некоторые существенные различия между конструкторами типов Haskell и Standard ML и подклассами C ++. (Примечание: ниже я использую стандартный синтаксис ML, потому что мне удобнее.)

  • В Haskell и Standard ML сопоставление с образцом может связать вложенные значения с переменными для вас (например, шаблон a::b::c::ds связывает первые три элемента списка с a, b и c, и остальная часть списка до ds). В C ++ вам все равно придется копаться в фактических вложенных структурах, если только вы или кто-то другой не придумаете гораздо более сложные макросы, чем было предложено здесь.
  • В Haskell и Standard ML объявление типа данных конструктора типа, подобное datatype 'a option = NONE | SOME of 'a, определяет один новый тип: 'a option. Конструкторы NONE и SOME не являются типами, это значения с типами 'a option и 'a -> 'a option соответственно. В C ++, когда вы определяете подклассы, такие как Foo и Bar для имитации конструкторов типов, вы получаете новые типы.
  • В Haskell и Standard ML такие конструкторы, как SOME, являются функциями первого класса, которые создают значения типа данных, к которому они принадлежат. Например, map SOME имеет тип 'a list -> 'a option list. В C ++, используя подклассы для симуляции конструкторов типов, вы не получаете эту возможность.
  • В Haskell и Standard ML типы данных закрыты, поэтому никто не может добавить больше конструкторов типов без изменения исходного объявления, и компилятор может проверить во время компиляции, что сопоставление с образцом обрабатывает все случаи. В C ++ вы должны стараться изо всех сил ограничивать тех, кто может создавать подклассы вашего базового класса.

В конце концов, получаете ли вы достаточно пользы от симулированного сопоставления с образцом по сравнению с использованием полиморфизма C ++ более типичным способом? Стоит ли использовать макросы для того, чтобы сделать симуляцию сопоставления с шаблоном немного более лаконичной (в то же время запутывая ее для всех, кто читает ваш код), стоит?

4 голосов
/ 01 апреля 2014

Мы в соавторстве создали библиотеку сопоставления с образцом для C ++, которая позволяет очень эффективно выполнять сопоставление с образцом и анализ типа. Библиотека под названием Mach7 была выпущена под лицензией BSD и доступна на GitHub: https://github.com/solodon4/Mach7. Здесь вы можете найти видео, плакаты, слайды, документы и исходный код. В настоящее время он поддерживает GCC 4.4+, Clang 3.4+ и Visual C ++ 2010+. Не стесняйтесь задавать вопросы о библиотеке, отправив вопрос GitHub в свой репозиторий.

3 голосов
/ 07 февраля 2010

Я предполагаю, что ваш шаблон Ptr имеет концепцию указателя NULL.

ObjectPtr ptr;
if(Ptr<Foo> p = ptr.convertAs<Foo>()) { // convertAs returns a NULL pointer if the conversion can't be done.
  ......
}
if(Ptr<Bar> p = ptr.convertAs<Bar>()) {
  ......
}

Хотя, как уже отмечали другие, включение типа обычно является признаком того, что вы делаете что-то не так в C ++. Вы должны рассмотреть возможность использования виртуальных функций.

2 голосов
/ 07 февраля 2010

Думаю, этот макрос делает именно то, что вы хотите:

#define DYN_IF(dest_type, dest_ptr, src_ptr)                                 \
    if((src_ptr).isType<dest_type>())                                        \
        if(int dest_type##dest_ptr = 1)                                      \
        for(Ptr<dest_type> dest_ptr = (src_ptr).convertAs<dest_type>();      \
            dest_type##dest_ptr;                                             \
            dest_type##dest_ptr=0)                                           

Использование:

ObjectPtr ptr;
DYN_IF(Foo, foo_ptr, ptr) { 
    // foo_ptr is Ptr<Foo>
}
DYN_IF(Bar, bar_ptr, ptr)  // Works without braces too for single statement 
    // bar_ptr is Ptr<Bar>

Я бы не советовал такого рода вещи в коде, который предназначен для чтения кем-то другим, но так как вы упомянули слово "макрос" ...

Кроме того, я бы не притворялся, что это как-то связано с сопоставлением с шаблоном в стиле Haskell / OCaml. Отметьте Scala, если вы хотите, чтобы язык имел семантику, аналогичную C ++ (ну, вроде) и истинное сопоставление с образцом.

...