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

#c #template-meta-programming

#c #шаблон-метапрограммирование

Вопрос:

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

 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
  

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

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

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

2. Обратите внимание, что для прямой взаимосвязи это уже возможно ( is_base_of может быть реализовано в терминах базовых блоков C 03)

3. Если есть 2 корня root1 и root2 , и child1 , child2 наследуют их оба ( struct child1 : root1, root2 {}; ), будет неоднозначно, closest_common_ancestor что возвращать.

Ответ №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 для реализации операций set.

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

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