Можно ли автоматически получить тип базового класса из типа шаблона? - PullRequest
9 голосов
/ 03 января 2012

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

class foo { public: char * Name() { return "foo"; }; };
class bar : public foo { public: char * Name() { return "bar"; }; };

template< typename T > struct ClassInfo { typedef T Base; };
template<> struct ClassInfo<bar> { typedef foo Base; };

int main()
{
  ClassInfo<foo>::Base A;
  ClassInfo<bar>::Base B;

  std::cout << A.Name();  //foo
  std::cout << B.Name();  //foo
}

на данный момент любой автоматический метод должен будет выбрать первую объявленную базу и потерпит неудачу для частных баз.

Ответы [ 6 ]

17 голосов
/ 03 января 2012

Это возможно с C ++ 11 и decltype.Для этого мы воспользуемся тем, что указатель на член не является указателем на производный класс, когда член наследуется от базового класса.

Например:

struct base{
    void f(){}
};
struct derived : base{};

Тип &derived::f будет void (base::*)(), а не void (derived::*)().Это было уже верно в C ++ 03, но было невозможно получить тип базового класса без его фактического указания.С decltype это просто и нуждается только в этой маленькой функции:

// unimplemented to make sure it's only used
// in unevaluated contexts (sizeof, decltype, alignof)
template<class T, class U>
T base_of(U T::*);

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

#include <iostream>

// unimplemented to make sure it's only used
// in unevaluated contexts (sizeof, decltype, alignof)
template<class T, class R>
T base_of(R T::*);

struct base{
    void f(){}
    void name(){ std::cout << "base::name()\n"; }
};
struct derived : base{
    void name(){ std::cout << "derived::name()\n"; }
};

struct not_deducible : base{
    void f(){}
    void name(){ std::cout << "not_deducible::name()\n"; }
};

int main(){
    decltype(base_of(&derived::f)) a;
    decltype(base_of(&base::f)) b;
    decltype(base_of(&not_deducible::f)) c;
    a.name();
    b.name();
    c.name();
}

Вывод:

base::name()
base::name()
not_deducible::name()

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

Однако есть и другие недостатки: член также должен однозначно идентифицировать члена базового класса:

struct base2{ void f(){} };

struct not_deducible2 : base, base2{};

int main(){
  decltype(base_of(&not_deducible2::f)) x; // error: 'f' is ambiguous
}

Это лучшее, что вы можете получить без поддержки компилятора.

5 голосов
/ 03 января 2012

Мои решения на самом деле не автоматические, но лучшее, что я могу придумать.

Интрузивное решение C ++ 03:

class B {};

class A : public B
{
public:
    typedef B Base;
};

Ненавязчивое решение C ++ 03:

class B {};

class A : public B {};

template<class T>
struct TypeInfo;

template<>
struct TypeInfo<A>
{
    typedef B Base;
};
5 голосов
/ 03 января 2012

Мне не известен ни один шаблон выбора базового класса, и я не уверен, что он существует или даже является хорошей идеей.Есть много способов, которыми это нарушает расширяемость и противоречит духу наследования.Когда bar публично наследует foo, bar является foo для всех практических целей, и клиентскому коду не нужно различать базовый класс и производный класс.

Публичный typedef в базовом классе часто царапает зуд, который вам может понадобиться, и более ясен:

class foo { public: typedef foo name_making_type; ... };

int main() {
    Foo::name_making_type a;
    Bar::name_making_type b;
}
2 голосов
/ 03 января 2012

Что с базовым классом ? Вы программист .NET или Java?

C ++ поддерживает множественное наследование, а также не имеет глобального общего базового класса. Таким образом, тип C ++ может иметь ноль, один или несколько базовых классов. Поэтому использование определенного товара противопоказано.

Поскольку базовый класс не имеет смысла, найти его невозможно.

1 голос
/ 30 марта 2017

С C ++ 11 вы можете создать навязчивый метод, чтобы всегда иметь член base_t, когда ваш класс наследует только от одного родителя:

template<class base_type>
struct labeled_base : public base_type
{
    using base_t = base_type; // The original parent type
    using base::base; // Inherit constructors

protected:
    using base = labeled_base; // The real parent type
};

struct A { virtual void f() {} };

struct my_class : labeled_base<A>
{
    my_class() : parent_t(required_params) {}

    void f() override
    {
        // do_something_prefix();
        base_t::f();
        // do_something_postfix();
    }
};

С этим классом у вас всегда будет псевдоним parent_t для вызова родительских конструкторов, как если бы это были конструкторы base с (возможно) более коротким именем и псевдонимом base_t, чтобы сделать ваш класс не знает имя типа базового класса, если оно длинное или сильно шаблонизированное.

Псевдоним parent_t защищен от несанкционированного доступа. Если вы не хотите, чтобы псевдоним base_t был общедоступным, вы всегда можете наследовать labeled_base как protected или private, нет необходимости изменять определение класса labeled_base.

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

0 голосов
/ 26 апреля 2016

Я ищу портативное решение для подобных проблем месяцами. Но пока не нашел.

G ++ имеет __bases и __direct_bases. Вы можете обернуть их в список типов, а затем получить доступ к любому из его элементов, например, std::tuple с std::tuple_element. См. libstdc ++ <tr2/type_traits> для использования.

Однако это не переносимо. Clang ++ в настоящее время не имеет таких встроенных функций.

...