派生类模板中的条件重写

时间:2018-10-23 19:48:19

标签: c++ c++11 templates inheritance sfinae

我有一个Container类,其中包含一些对象,这些对象的类型可以从某些基类(TypeATypeB等)的任何组合中派生。 Container的基类具有虚方法,这些虚方法返回指向所包含对象的指针。如果所包含的对象不是从预期的类派生的,则这些应返回nullptr。我想根据Container的模板参数有选择地重写base的方法。我尝试按如下方式使用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 :(得分:3)

抢救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>);

两者都很好。

Live example -它确实使用了一种或两种C ++ 14功能(即tag中的变量模板),但是您可以将tag<X>替换为tag_t<X>{}。 / p>

答案 1 :(得分:3)

...或者您可以将方法更改为更简单的方法:

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,则删除无效的代码分支。

编辑:

...或(如果您坚持使用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;
};

答案 2 :(得分:2)

  

我尝试如下使用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()现在是模板方法,因此不再是虚拟的。

如果您确实需要虚函数,则可以通过继承和模板专门化来解决(请参阅Yakk的答案)。

但是,如果您真的不希望get_TypeX()函数是虚拟的,我建议您使用完全不同的解决方案(我想更简单),该解决方案完全基于几个(无论{{1 }}类)。

我的意思是...如果您编写以下两种可选的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

以下是一个完整的C ++ 14示例(如果您需要C ++ 11解决方案,只需使用typea.get_Type<TypeA>() 而不是typename std::enable_if<>::type

std::enable_if_t<>