我需要存储指向实例化模板函数的指针,当无法实例化函数时,我想存储指向空函数的指针。我查看了 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()];
然后在运行时使用类型 ID 调用正确的函数:
funcs[entity->getTypeId()][system->getTypeId()](&system, &entity, dt);
在运行时,我将检查实体是否与系统兼容,但它是运行时信息。因此,必须在编译时为所有实体系统对注册所有函数指针,即使它们不兼容。这是我想创建那些无操作函数的地方。
最佳答案
首先,元编程样板:
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
将该应用程序转换为编译时 bool 测试的别名。
顺便说一句,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
如果您的编译器不支持,则进行类似的其他更改。
关于c++ - 当模板函数实例化失败时回退到备用函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31302777/