Как стать автором
Обновить

Комментарии 19

Мне кажется, если требуется такое, обычно что-то не так с архитектурой
На самом деле не всегда — иногда нужно дать более простой и наглядный способ выполнять сложные вещи. Если у вас достаточно времени и знаний то такие трюки снабжённые коротким комментарием гораздо лучше разлапистой документации когда и какую вещь следует использовать.
Хочется понять реальный пример, когда такое необходимо. В постановке задачи сказано, что «поведение зависит от того, какие классы являются базовыми для обрабатываемого класса». А когда такое вообще нужно? Какова исходная постановка задачи?

Создается ощущение, что в постановку задачи проник способ решения, что выглядит странным.
Есть примерно такая ситуация:

struct Interface
{
    void getIds(unsigned& id0, unsigned& id1) = 0;
};

struct HasMainId0
{
    unsigned id0;
};

struct HasMainId1: public HasMainId0
{
    unsigned id1;
};

struct HasAnotherId
{
    unsigned anotherId;
}

struct Data1: HasMainId0 {};

struct Data2: HasMainId1 {};

struct Data3: HasAnotherId {};

struct Data4: HasMainId0, HasAnotherId {};

template<typename T>
struct DynamicData: public Interface, public T
{
    void getIds(unsigned& id0, unsigned& id1);
}

typedef DynamicData<Data1> DynamicData1;
typedef DynamicData<Data2> DynamicData2;
typedef DynamicData<Data3> DynamicData3;
typedef DynamicData<Data4> DynamicData4;


Метод getIds должен возвращать значения:
HasMinaId0::id0 и 0xFFFFFFFF если класс T унаследован от HasMainId0
HasMinaId0::id0 и 0xFFFFFFFF если класс T унаследован от HasMainId0 и HasAnotherId
HasAnotherId::anotherId и 0xFFFFFFFF если класс T унаследован от HasAnotherId
HasMainId0::id0 и HasMainId1::id1 если класс T унаследован от HasMainId1

структур Has… больше чем три :) но в этом месте обрабатываются только эти. Для реализации метода getIds и был написан Executor подобный описанному:

template<typename T, typename F>
struct GetterIds
{
    void operator()( const T& val, unsigned& id0, unsigned& id1 );
};

void selector(... );
HasMainId0 selector(HasMainId0*, ... );
HasMainId0 selector(HasMainId0*, HasAnotherId* );
HasAnotherId selector(HasAnotherId*, ... );
HasMainId1 selector(HasMainId1*, ... );



Общая архитектура досталась в наследство, только базовых структур не было, все поля содержались в структурах Data1, Data2, ..., DataN. И использовалось несколько шаблонных классов в зависимости от того какие параметры содержала та или иная «Data» и в них по разному был реализован метод getIds.

В этом конкретном месте такой вариант проверки наследования мне показался удобным и более коротким по записи. Там где идет обработка по всем «базовым» структурам я использую списки типов.

Я может не вник до конца, но мне кажется всю логику Executor нужно просто засунуть в базовые классы, например.

Общая архитектура досталась в наследство
Т.е. предложенный метод в какой-то степени является замысловатым костылем.
Придется писать в структурах Data1...DataN практически один и тот же код для метода getIds. В предложенном мной варианте, есть несколько возможных имплементаций которые собраны в одном месте. И не надо задумывать, при добавлении новой DataN+1, какой должна быть реализация метода getIds.

Т.е. предложенный метод в какой-то степени является замысловатым костылем.


Это не костыль, это замена ручного указания на то как должен быть реализован метод. DynamicData формируется при помощи макросов, для каждого варианта DataM в макрос передавалось количество id (от 0 до 2), которое содержится в DataM. И для каждого количества была отдельная реализация шаблонного класса.

А общая архитектура хоть и имеет некоторые недостатки, но в целом доказала свою жизнеспособность. Рефакторинг производился не самой архитектуры в целом, а механизма извлечения дополнительной информации иэ структур DataM. И заодно немного автоматизировал получение базовой информации.
А если так:
struct HasAnotherId
{
  virtual void getIds(unsigned& id0, unsigned& id1)
  {
    id0 = HasAnotherId::anotherId; id1=0xFFFFFFFF;
  }
}

struct HasMainId0 : public HasAnotherId
{
  virtual void getIds(unsigned& id0, unsigned& id1)
  {
    id0 = HasMainId0::id0; id1=0xFFFFFFFF;
  }
}

struct HasMainId1: public HasMainId0
{
  virtual void getIds(unsigned& id0, unsigned& id1)
  {
    id0 = HasMainId0::id0; id1=HasMinaId1::id1;
  }
}
Прошу прощения, забыл указать вариант когда нет наследования ни от HasMainId0, HasMainId1, HasAnotherId, тогда возвращается для обоих 0xFFFFFFFF.

Да, такой вариант тоже возможен. Единственное, что так как есть еще общий интерфейс в шаблоне для DynamicData нужно будет его реализовать и вызывать из него уже getIds реализованый в DataM. А для случая который я забыл указать можно добавить наследование от структуры.

struct NoIds
{
  virtual void getIds(unsigned& id0, unsigned& id1)
  {
    id0 = 0xFFFFFFFF; id1=0xFFFFFFFF;
  }
}


Но такой вариант мне кажется менее красивым.
Красота конечно понятие растяжимое, но вот это вот:
Executor<T, sizeof( selector( (T*) 0, (T*) 0 ) )>()( v );
По мне так больше похоже на regexp чем на C++. Представьте кто-то кроме вас попробует разобраться? Вы тогда в комменты к коду эту статью добавьте :)
Пока я писал код и статью, мне это казалось прозрачным :). Но ваш вопрос заставил сомневаться и пересмотреть код еще раз. Пожалуй в указанном месте можно вообще было обойтись без указателей:

selector 
Executor<T, sizeof( selector( v, v ) )>()( v );


Но Ваш вариант, действительно, гораздо проще. Увлекся я что-то с шаблонами не к месту :)
Ваш вариант с академической точки зрения интереснее. А также, если код базовых класов недоступен для редактирования.
Спасибо.
template<class T>
typename std::enable_if<std::is_base_of<Base1,T>::value,void>::type
execute(const T&);

template<class T>
typename std::enable_if<std::is_base_of<Base2,T>::value,void>::type
execute(const T&);
Два void'а необязательны
В случае проверки на наследование от одного класса указанные Вами пример проще, но как только появляется необходимость проверки на одновременное наследование от нескольких классов такой вариант становиться более громоздкий, чем вариант описанные мной в публикации.

И опять же надо решать проблему когда класс T одновременно унаследован от Base1 и Base2.
Можно же любое условие задать, например, «унаследован от Base1, но не от Base2»:
typename std::enable_if<
        std::is_base_of<Base1,T>::value && !std::is_base_of<Base2,T>::value
    >::type
Спасибо, так действительно проще для восприятия и такой вариант более гибкий. Похоже мой вариант больше подходит когда стандарт С++11 не доступен и не используется boost.
Наверное, не Deliver, а Derived?
Вы правы, спасибо, исправил.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории