Как вывести во время компиляции корень дерева наследования, общий для двух типов, если он существует?

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

template<typename T1, typename T2>
  struct closest_common_ancestor
{
  typedef XXX type;  // what goes here?
};

Учитывая следующие виды:

struct root {};
struct child1 : root {};
struct child2 : root {};
struct child3 : child2 {};
struct unrelated {};

closest_common_ancestor приведет к следующим типам:

closest_common_ancestor<root, child1>::type == root
closest_common_ancestor<child1, child2>::type == root
closest_common_ancestor<child3, child1>::type == root
closest_common_ancestor<child3, child2>::type == child2
closest_common_ancestor<unrelated, child1>::type == error

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


person Jared Hoberock    schedule 03.11.2011    source источник
comment
Вы не можете проверять базовые классы. Нет, если только вы вручную не добавите метаинформацию в каждый из ваших классов.   -  person K-ballo    schedule 03.11.2011
comment
Обратите внимание, что для прямой связи это уже возможно (is_base_of может быть реализовано с точки зрения базовых блоков C++03)   -  person Matthieu M.    schedule 03.11.2011
comment
Если есть 2 корня root1 и root2, а child1, child2 наследуют их оба (struct child1 : root1, root2 {};), то для closest_common_ancestor будет неоднозначно что возвращать.   -  person kennytm    schedule 04.11.2011


Ответы (1)


Как упомянул K-ballo, к сожалению, невозможно получить список баз, которые есть у класса (жаль...).

Если вы вручную аннотируете свои классы (скажем, определяете простой std::tuple<> список баз), вы можете использовать эту информацию. Проще, конечно, было бы использовать трейт:

template <typename> struct list_bases { typedef std::tuple<> type; };

Затем вы можете специализировать эту черту для своих типов:

template <> struct list_bases<child1> { typedef std::tuple<root> type; };

И, начиная с этого момента, вы можете начать экспериментировать с поиском предка... однако это может быть не сразу. Помимо деталей реализации (рекурсивное получение баз, реализация выбора «расстояния»), я ожидаю проблемы со «странными» случаями.

Выбор расстояния в обычной (линейной) иерархии наследования можно решить, используя комбинацию is_base_of и is_same, однако рассмотрим следующую иерархию:

struct root1 {}; struct root2 {};

struct child1: root1 {}; struct child2: root2 {};

struct child12: root1, child2 {}; struct child21: root2, child1 {};

Итак, у child12 и child21 есть два общих предка: root1 и root2... кто ближе?

Они эквивалентны. Учтите, что я добавляю:

struct root3 {}; struct child31: root3, child1 {};

Тогда root1 является общим предком child12, child21 и child31.

Однако, если бы я обошёл определение closest_common_ancestor и произвольно определил, что closest_common_ancesotr<child12, child21> это root2, то я не смог бы найти никакого общего предка с child31.

Поэтому я предлагаю составить список всех ближайших предков и использовать tuple для реализации операций над множествами.

person Matthieu M.    schedule 03.11.2011
comment
Спасибо. Для моего приложения я бы разрешил упомянутую вами двусмысленность, настаивая на том, что все рассматриваемые типы имеют только нулевой или один базовый тип. - person Jared Hoberock; 04.11.2011