Определение отношений наследования среди стертых типов

У меня есть типичная настройка стирания типа:

struct TEBase
{
virtual ~TEBase() {}
// ...
};

template <typename T>
struct TEImpl : TEBase
{
// ...
};

Теперь вопрос: учитывая иерархию второго класса, как это,

struct Foo { };
struct Bar : Foo { };

struct Unrelated { };

это возможно, учитывая TEBase * p, чтобы определить, является ли динамический тип *p имеет форму TEImpl<X>, где, X происходит от Foo? Другими словами, я хочу функцию:

template <typename T> bool is_derived_from(TEBase * p);

такой что:

is_derived_from<Foo>(new TEImpl<Foo>) == true
is_derived_from<Foo>(new TEImpl<Bar>) == true

is_derived_from<Foo>(new TEImpl<Unrelated>) == false

В частности, я ищу решение, которое является общим, ненавязчивым и эффективным. Я нашел два решения этой проблемы (опубликовано ниже как ответы), но ни один из них не решает все три критерия.

4

Решение

Что-то вроде этого:

template <typename Type, typename UnaryPredicate>
void DoPred(UnaryPredicate pred)
{
if (T * p = dynamic_cast<Derived<T> *>(this))
{
return pred(p->type);
}
return false;
}

Это не на 100% универсально, так как вы не можете, например, сказать DoPred<int>, Более универсальное решение добавило бы virtual std::type_info type() const { return typeid(...); } функция-член в иерархии и использования тот определить, соответствует ли тип (идиома стирания стандартного типа). Оба подхода используют один и тот же вид RTTI.


После уточнения:

Сейчас я не думаю, что это можно решить. Все, что у вас есть, это TEBase субобъект. Это может быть частью TEImpl<Bar>или часть TEImpl<Unrelated>, но ни один из этих типов не связан с TEImpl<Foo>, то, что вы после.

Вы по сути спрашиваете, что TEImpl<Bar> производный от TEImpl<Foo>, Чтобы сделать это, вы бы на самом деле хотели TEImpl<T> в унаследовать из всех TEImpl<std::direct_bases<T>::type>..., если вы понимаете, о чем я. Это невозможно в C ++ 11, но будет возможно в TR2. GCC уже поддерживает это. Вот пример реализации. (Это вызывает предупреждение из-за неоднозначных оснований, которых можно было бы избежать с помощью большей работы, но, тем не менее, это работает.)

#include <tr2/type_traits>

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

template <typename T> struct TEImpl;

template <typename TL> struct Derivator;

template <typename TL, bool EmptyTL>
struct DerivatorImpl;

template <typename TL>
struct DerivatorImpl<TL, true>
: TEBase
{ };

template <typename TL>
struct DerivatorImpl<TL, false>
: TEImpl<typename TL::first::type>
, Derivator<typename TL::rest::type>
{ };

template <typename TL>
struct Derivator
: DerivatorImpl<TL, TL::empty::value>
{ };

template <typename T>
struct TEImpl
: Derivator<typename std::tr2::direct_bases<T>::type>
{
};

template <typename T>
bool is(TEBase const * b)
{
return nullptr != dynamic_cast<TEImpl<T> const *>(b);
}struct Foo {};
struct Bar : Foo {};
struct Unrelated {};

#include <iostream>
#include <iomanip>

int main()
{
TEImpl<int> x;
TEImpl<Unrelated> y;
TEImpl<Bar> z;
TEImpl<Foo> c;

std::cout << std::boolalpha << "int ?< Foo: " << is<Foo>(&x) << "\n";
std::cout << std::boolalpha << "Unr ?< Foo: " << is<Foo>(&y) << "\n";
std::cout << std::boolalpha << "Bar ?< Foo: " << is<Foo>(&z) << "\n";
std::cout << std::boolalpha << "Foo ?< Foo: " << is<Foo>(&c) << "\n";
}
2

Другие решения

Я бы предложил прочитать статью Общее программирование: списки типов и приложения. Там Андрей Александреску описывает реализацию специального посетителя, который должен решить вашу проблему. Еще одним хорошим источником будет его книга Модер C ++ Design где он описывает мультидиспетчер методом грубой силы, который использует тот же метод (стр. 265 …).

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

1

Это решение включает в себя немного злоупотребление исключениями. Если TEImpl Тип просто выдает свои данные, is_derived_from может поймать тип, который он ищет.

struct TEBase
{
virtual ~TEBase() {}

virtual void throw_data() = 0;
};

template <typename T>
struct TEImpl : public TEBase
{
void throw_data() {
throw &data;
}

T data;
};

template <typename T>
bool is_derived_from(TEBase* p)
{
try {
p->throw_data();
} catch (T*) {
return true;
} catch (...) {
// Do nothing
}
return false;
}

Это решение прекрасно работает. Он отлично работает с любой структурой наследования и абсолютно не навязчив.

Единственная проблема заключается в том, что это не эффективно вообще. Исключения не были предназначены для использования таким образом, и я подозреваю, что это решение в тысячи раз медленнее, чем другие решения.

0

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

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

struct TEBase
{
virtual ~TEBase() {}

virtual bool is_type(const type_info& ti) = 0;
};

template <typename T>
struct TEImpl : public TEBase
{
bool is_type(const type_info& ti) {
return is_type_impl<T>(ti);
}

template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
return is_type_super<Haystack>(ti, nullptr);
}

template <typename Haystack>
static bool is_type_super(const type_info& ti, typename Haystack::super*) {
if(typeid( Haystack ) == ti) return true;
return is_type_impl<typename Haystack::super>(ti);
}

template <typename Haystack>
static bool is_type_super(const type_info& ti, ...) {
return typeid(Haystack) == ti;
}
};

template <typename T>
bool is_derived_from(TEBase* p)
{
return p->is_type(typeid( T ));
}

Чтобы это работало, Bar должен быть переопределен как:

struct Bar : public Foo
{
typedef Foo super;
};

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

Это также не имеет дело с множественным наследованием вообще.

Обновить: Это решение может быть принято далее, чтобы сделать его общим и ненавязчивым.

Делая это вообще

typedef super это здорово, потому что это идиоматично и уже используется во многих классах, но не допускает множественного наследования. Чтобы сделать это, нам нужно заменить его типом, который может хранить несколько типов, например кортеж.

Если Bar был переписан как:

struct Bar : public Foo, public Baz
{
typedef tuple<Foo, Baz> supers;
};

мы могли бы поддержать эту форму объявления, добавив следующий код в TEImpl:

template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
// Redefined to call is_type_supers instead of is_type_super
return is_type_supers<Haystack>(ti, nullptr);
}

template <typename Haystack>
static bool is_type_supers(const type_info& ti, typename Haystack::supers*) {
return IsTypeTuple<typename Haystack::supers, tuple_size<typename Haystack::supers>::value>::match(ti);
}

template <typename Haystack>
static bool is_type_supers(const type_info& ti, ...) {
return is_type_super<Haystack>(ti, nullptr);
}

template <typename Haystack, size_t N>
struct IsTypeTuple
{
static bool match(const type_info& ti) {
if(is_type_impl<typename tuple_element< N-1, Haystack >::type>( ti )) return true;
return IsTypeTuple<Haystack, N-1>::match(ti);
}
};

template <typename Haystack>
struct IsTypeTuple<Haystack, 0>
{
static bool match(const type_info& ti) { return false; }
};

Делать это ненавязчивым

Теперь у нас есть решение, которое является эффективным и общим, но все еще навязчивым, поэтому оно не будет поддерживать классы, которые нельзя изменить.

Для поддержки этого нам понадобится способ объявить наследование объекта извне класса. За Fooмы могли бы сделать что-то вроде этого:

template <>
struct ClassHierarchy<Bar>
{
typedef tuple<Foo, Baz> supers;
};

Для поддержки этого стиля сначала нам понадобится неспециализированная форма ClassHierarchy, которую мы определим так:

template <typename T> struct ClassHierarchy { typedef bool undefined; };

Мы будем использовать наличие undefined сказать, был ли класс специализированным.

Теперь нам нужно добавить еще несколько функций в TEImpl. Мы по-прежнему будем использовать большую часть кода ранее, но теперь мы также будем поддерживать чтение данных типа из ClassHierarchy,

template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
// Redefined to call is_type_external instead of is_type_supers.
return is_type_external<Haystack>(ti, nullptr);
}

template <typename Haystack>
static bool is_type_external(const type_info& ti, typename ClassHierarchy<Haystack>::undefined*) {
return is_type_supers<Haystack>(ti, nullptr);
}

template <typename Haystack>
static bool is_type_external(const type_info& ti, ...) {
return is_type_supers<ClassHierarchy< Haystack >>(ti, nullptr);
}

template <typename Haystack>
struct ActualType
{
typedef Haystack type;
};

template <typename Haystack>
struct ActualType<ClassHierarchy< Haystack >>
{
typedef Haystack type;
};

template <typename Haystack>
static bool is_type_super(const type_info& ti, ...) {
// Redefined to reference ActualType
return typeid(typename ActualType<Haystack>::type) == ti;
}

И теперь у нас есть решение, которое является эффективным, общим и ненавязчивым.

Будущее решение

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

Предлагаемое решение этой проблемы N2965: Тип черты и базовые классы, который был реализован в GCC. Этот документ определяет direct_bases класс, который практически идентичен нашему ClassHierarchy класс, кроме его единственного элемента, type, гарантированно будет кортежем, как supersи класс полностью генерируется компилятором.

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

Отдельное спасибо Kerrek SB и Jan Herrmann. Этот ответ черпал много вдохновения из их комментариев.

0
По вопросам рекламы [email protected]