派生 class 模板中的条件覆盖
Conditional override in derived class template
我有一个 Container
class,它保存的对象的类型可能来自某些基础 classes(TypeA
、TypeB
的任意组合, 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;
};
我有一个 Container
class,它保存的对象的类型可能来自某些基础 classes(TypeA
、TypeB
的任意组合, 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;
};