c++ - 当模板函数实例化失败时回退到备用函数

标签 c++ templates c++11

我需要存储指向实例化模板函数的指针,当无法实例化函数时,我想存储指向空函数的指针。我查看了 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_updatehas_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/

相关文章:

c++ - 在关联容器中使用 emplace(args&& ...)

c++ - 在c++中使用异常处理的一个小问题

c++ - 组织 C++ 套接字服务器的最佳方式是什么?

c++ - 如何在 Qt 中进行 MySQL 查询?

c++ - 解释完模板后如何处理学生?

c++ - C++参数包类型扩展

c++ - 如何编写一个不断敲击输出的包装器?

c++ - static_cast 没有按预期处理优先级

C++ 在编译时计算和排序 vector

c++ - 从 char* 创建 std::string 时在末尾获取额外字符