Условное переопределение в шаблоне производного класса - PullRequest
0 голосов
/ 23 октября 2018

У меня есть класс Container, который содержит объекты, тип которых может быть получен из любой комбинации некоторых базовых классов (TypeA, TypeB и т. Д.).Базовый класс Container имеет виртуальные методы, которые возвращают указатель на содержащийся объект;они должны возвращать nullptr, если содержащийся объект не является производным от ожидаемого класса.Я хотел бы выборочно переопределить методы базы на основе параметра шаблона Container.Я попытался использовать SFINAE следующим образом, но он не компилируется.Я хотел бы избежать специализации Container для каждой возможной комбинации, потому что их может быть много.

#include <type_traits>
#include <iostream>

using namespace std;

class TypeA {};
class TypeB {};
class TypeAB: public TypeA, public TypeB {};

struct Container_base {
    virtual TypeA* get_TypeA() {return nullptr;}
    virtual TypeB* get_TypeB() {return nullptr;}
};

template <typename T>
struct Container: public Container_base
{
    Container(): ptr(new T()) {}

    //Override only if T is derived from TypeA
    auto get_TypeA() -> enable_if<is_base_of<TypeA, T>::value, TypeA*>::type
    {return ptr;}

    //Override only if T is dervied from TypeB
    auto get_TypeB() -> enable_if<is_base_of<TypeB, T>::value, TypeB*>::type
    {return ptr;}

private:
    T* ptr;
};

int main(int argc, char *argv[])
{
    Container<TypeA> typea;
    Container<TypeB> typeb;
    Container<TypeAB> typeab;

    cout << typea.get_TypeA() << endl; //valid pointer
    cout << typea.get_TypeB() << endl; //nullptr

    cout << typeb.get_TypeA() << endl; //nullptr
    cout << typeb.get_TypeB() << endl; //valid pointer

    cout << typeab.get_TypeA() << endl; //valid pointer
    cout << typeab.get_TypeB() << endl; //valid pointer

    return 0;
}

Ответы [ 3 ]

0 голосов
/ 23 октября 2018

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

К сожалению, виртуальные функции и функции шаблона несовместимы.И вы не можете использовать SFINAE с не шаблонными методами, поэтому

auto get_TypeA()
   -> typename std::enable_if<std::is_base_of<TypeA, T>::value, TypeA*>::type
 {return ptr;}

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

Чтобы включить SFINAE, вы можете шаблонизировать метод следующим образом

template <typename U = T>
auto get_TypeA()
   -> typename std::enable_if<std::is_base_of<TypeA, U>::value, TypeA*>::type
 {return ptr;}

и теперь SFINAE работает, но get_TypeA() теперь является шаблоном, поэтому больше не может быть виртуальным.

Если вам действительно нужны виртуальные функции, вы можете решить с помощью наследования и специализации шаблонов (см. Ответ Якка).

Но, если вам действительно не нужно, чтобы get_TypeX() функции были виртуальными, я предлагаю вамсовершенно другое (и более простое, я полагаю) решение, полностью основанное на паре (независимо от количества TypeX классов) шаблонных методов.

Я имею в виду ... если вы напишите пару альтернативных get_Type() шаблонные методы выглядят следующим образом

  template <typename U>
  auto get_Type()
     -> std::enable_if_t<true == std::is_base_of<U, T>::value, U*>
   { return ptr; }

  template <typename U>
  auto get_Type()
     -> std::enable_if_t<false == std::is_base_of<U, T>::value, U*>
   { return nullptr; }

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

typea.get_Type<TypeA>()

Ниже приведен полностью рабочий пример C ++ 14 (если вам нужно решение C ++ 11, просто используйте typename std::enable_if<>::type вместо std::enable_if_t<>)

#include <type_traits>
#include <iostream>

class TypeA {};
class TypeB {};
class TypeAB: public TypeA, public TypeB {};

template <typename T>
struct Container
 {
   private:
      T* ptr;

   public:
      Container(): ptr{new T{}} {}

      template <typename U>
      auto get_Type()
         -> std::enable_if_t<true == std::is_base_of<U, T>::value, U*>
       { return ptr; }

      template <typename U>
      auto get_Type()
         -> std::enable_if_t<false == std::is_base_of<U, T>::value, U*>
       { return nullptr; }
};

int main ()
 {
   Container<TypeA> typea;
   Container<TypeB> typeb;
   Container<TypeAB> typeab;

   std::cout << typea.get_Type<TypeA>() << std::endl; //valid pointer
   std::cout << typea.get_Type<TypeB>() << std::endl; //nullptr

   std::cout << typeb.get_Type<TypeA>() << std::endl; //nullptr
   std::cout << typeb.get_Type<TypeB>() << std::endl; //valid pointer

   std::cout << typeab.get_Type<TypeA>() << std::endl; //valid pointer
   std::cout << typeab.get_Type<TypeB>() << std::endl; //valid pointer
 }
0 голосов
/ 24 октября 2018

... или вы можете изменить свой подход на более простой:

template <typename T>
struct Container: public Container_base
{
    TypeA* get_TypeA() override
    {
        if constexpr(is_base_of_v<TypeA, T>)
            return ptr;
        else
            return nullptr;
    }

    ...
};

и положиться на оптимизатор, чтобы сгладить любые морщины.Как замена нескольких return nullptr функций одной (в конечном двоичном файле).Или удаление мертвой ветви кода, если ваш компилятор не поддерживает if constexpr.

Edit:

... или (если вы настаиваете на использовании SFINAE) что-товдоль этих линий:

template<class B, class T, enable_if_t< is_base_of_v<B, T>>...> B* cast_impl(T* p) { return p; }
template<class B, class T, enable_if_t<!is_base_of_v<B, T>>...> B* cast_impl(T* p) { return nullptr; }

template <typename T>
struct Container: public Container_base
{
    ...

    TypeA* get_TypeA() override { return cast_impl<TypeA>(ptr); }
    TypeB* get_TypeB() override { return cast_impl<TypeB>(ptr); }

private:
    T* ptr;
};
0 голосов
/ 23 октября 2018

CRTP на помощь!

template<class T, class D, class Base, class=void>
struct Container_getA:Base {};
template<class T, class D, class Base, class=void>
struct Container_getB:Base {};

template<class T, class D, class Base>
struct Container_getA<T, D, Base, std::enable_if_t<std::is_base_of<TypeA,T>{}>>:
  Base
{
  TypeA* get_TypeA() final { return self()->ptr; }
  D* self() { return static_cast<D*>(this); }
};

template<class T, class D, class Base>
struct Container_getB<T, D, Base, std::enable_if_t<std::is_base_of<TypeB,T>{}>>:
  Base
{
  TypeB* get_TypeB() final { return self()->ptr; }
  D* self() { return static_cast<D*>(this); }
};

template <class T>
struct Container: 
  Container_getA< T, Container<T>,
    Container_getB< T, Container<T>,
      Container_base
    >
  >
{
    Container(): ptr(new T()) {}

public: // either public, or complex friend declarations; just make it public
    T* ptr;
};

и готово.

Вы можете проделать небольшую работу, чтобы разрешить:

struct Container: Bases< T, Container<T>, Container_getA, Container_getB, Container_getC >

или тому подобное, где мысложите базы CRTP.

Вы также можете очистить свой синтаксис:

template<class...Ts>
struct types {};

template<class T>
struct tag_t {using type=T;};
template<class T>
constexpr tag_t<T> tag{};

Тогда вместо набора именованных получателей получите:

template<class List>
struct Container_getters;

template<class T>
struct Container_get {
  virtual T* get( tag_t<T> ) { return nullptr; }
};
template<class...Ts>
struct Container_getters<types<Ts...>>:
  Container_get<Ts>...
{
   using Container_get<Ts>::get...; // C++17
   template<class T>
   T* get() { return get(tag<T>); }
};

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

Затем мы можем использовать этот центральный список типов для написания промежуточных помощников CRTP.

template<class Actual, class Derived, class Target, class Base, class=void>
struct Container_impl_get:Base {};
template<class Actual, class Derived, class Target, class Base>
struct Container_impl_get<Actual, Derived, Target, Base,
  std::enable_if_t<std::is_base_of<Target, Actual>{}>
>:Base {
  using Base::get;
  virtual Target* get( tag_t<Target> ) final { return self()->ptr; }
  Derived* self() { return static_cast<Derived*>(this); }
};

и теперь нам просто нужно написать механизм сгиба.

template<class Actual, class Derived, class List>
struct Container_get_folder;
template<class Actual, class Derived, class List>
using Container_get_folder_t=typename Container_get_folder<Actual, Derived, List>::type;

template<class Actual, class Derived>
struct Container_get_folder<Actual, Derived, types<>> {
  using type=Container_base;
};
template<class Actual, class Derived, class T0, class...Ts>
struct Container_get_folder<Actual, Derived, types<T0, Ts...>> {
  using type=Container_impl_get<Actual, Derived, T0,
    Container_get_folder_t<Actual, Derived, types<Ts...>>
  >;
};

, чтобы мы получили

using Container_types = types<TypeA, TypeB, TypeC>;
struct Container_base:Container_getters<Container_types> {
};

template <typename T>
struct Container: Container_get_folder_t<T, Container<T>, Container_types>
{
    Container(): ptr(new T()) {}
    T* ptr;
};

, и теперь мы можем расширить это, просто добавив тип к Container_types.

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

Container_base* ptr = /* whatever */;
ptr->get<TypeA>()

или

ptr->get(tag<TypeA>);

оба работают одинаково хорошо.

Живой пример - он использует одну или две функции C ++ 14 (а именно шаблоны переменных в tag), но вы можете заменить tag<X> на tag_t<X>{}.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...