我需要存储指向实例模板函数的指针,当函数无法实例化时,我想将指针存储为空函数。我调查了SFINAE,但我不认为它适用于此。
struct StaticEntity {
double position;
};
struct DynamicEntity {
double position;
double speed;
};
class MoveSystem {
public:
template <typename T>
void update(T& entity, double dt) {
entity.position += entity.speed*dt;
}
};
typedef void (*updateEntitiesFunc)(void* system, void* entity, double dt);
template <typename S, typename E>
static void update(void* system, void* entity, double dt)
{
// here if inner function cannot be instanced i would like to skip it and do "nothing" instead
((S*)system)->update(*(E*)entity, dt);
}
int main() {
updateEntitiesFunc uf = update<MoveSystem, DynamicEntity>;
updateEntitiesFunc uf2 = update<MoveSystem, StaticEntity>;
//^ this does not compile
// gives error: 'struct StaticEntity' has no member named 'speed'
// i would like it to compile and contain pointer to empty function
return 0;
}
它可能是一些我无法弄清楚的模板魔法可以解决的。 理想情况下,不会增加实体和系统类的复杂性。
设计动机:
对于我的所有实体和系统类型,我想创建一个函数指针的静态数组:
updateEntitiesFunc funcs[EntityTypes::getTypesCount()][SystemTypes::getTypesCount()];
然后在运行时使用type-id调用正确的函数:
funcs[entity->getTypeId()][system->getTypeId()](&system, &entity, dt);
在运行时,我将检查实体是否与系统兼容,但它是运行时信息。因此,所有函数指针必须在编译时为所有实体系统对注册,即使它们不兼容。这是我想要创建那些无操作功能的地方。
答案 0 :(得分:4)
首先,元编程样板:
namespace details {
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<template<class...>class Z, class, class...Ts>
struct can_apply:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
现在,我们可以检测属性:
template<class T>
using speed_t = decltype(std::declval<T>().speed);
template<class T>
using position_t = decltype(std::declval<T>().position);
template<class T>
using has_speed = can_apply<speed_t, T>;
template<class T>
using has_position = can_apply<position_t, T>;
template<class S, class E>
using update_call_t = decltype( std::declval<S>().update( std::declval<E>(), 0.0 ) );
template<class S, class E>
using has_update = can_apply< update_call_t, S, E >;
我们有三个特征,has_position
,has_update
和has_speed
非常有用。
现在我们修复MoveSystem
:
struct MoveSystem {
template <class T>
std::enable_if_t< has_speed<T&>{} && has_position<T&>{} >
update(T& entity, double dt) {
entity.position += entity.speed*dt;
}
};
接下来,我们修改更新:
namespace updates {
template<class S, class E>
std::enable_if_t< has_update<S,E>{} >
update(S* system, E* entity, double dt ) {
system->update(*entity, dt);
}
void update(void*, void*, double) {}
}
template<class S, class E>
void update(void* system, void* entity, double dt) {
using updates::update;
update(static_cast<S*>(system), static_cast<E*>(entity), dt );
}
检查使用这些参数的.update
方法。
我启用了ADL代码,如果该类有friend void update( S*, E*, double )
,它也可以工作。
这是SFINAE的全部工作。请注意,一旦我们拥有can_apply
,添加更多属性非常简单。创建一个别名,该别名生成仅在满足属性时才有效的类型,然后编写一个can_apply
别名,将该应用程序转换为编译时布尔测试。
顺便说一下,MSVC2015不是C ++ 11编译器,因为它无法编译上面的代码。在MSVC中,您必须跟踪一些专有扩展以执行与上述代码相同的操作。这涉及以不同方式编写has_position
和其他特征。他们称在这种情况下无法遵守C ++ 11标准,无法做“表达SFINAE”。
请注意,上面使用了一些C ++ 14功能。将std::enable_if_t<??>
替换为typename std::enable_if<??>::type
,将has_position<??>{}
替换为has_position<??>::value
以及类似的其他更改(如果您的编译器不支持它)。