我有一个Container
类,其中包含一些对象,这些对象的类型可以从某些基类(TypeA
,TypeB
等)的任何组合中派生。 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;
}
答案 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<>