派生 class 模板中的条件覆盖

Conditional override in derived class template

我有一个 Container class,它保存的对象的类型可能来自某些基础 classes(TypeATypeB 的任意组合, ETC。)。 Container 的基础 class 具有虚拟方法,return 指向包含对象的指针;如果包含的对象不是从预期的 class 派生的,这些应该 return 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;
}

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{};

然后,不再有一堆命名的 getter,而是:

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>{} .

I tried using SFINAE as follows, but it doesn't compile. I would like to avoid specializing Container for every possible combination because there could be many.

不幸的是,虚函数和模板函数不兼容。而且您不能将 SFINAE 与非模板方法一起使用,所以

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

不起作用,因为 T 类型是 class 的模板参数,而不是方法的模板参数。

要启用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() 函数是虚拟的,我建议您使用一个完全不同(我想更简单)的解决方案,完全基于一对夫妇(无论 TypeX classes) 模板方法。

我的意思是......如果你写几个替代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
 }

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

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;
};