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

时间:2015-07-08 20:12:39

标签: 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()];

然后在运行时使用type-id调用正确的函数:

funcs[entity->getTypeId()][system->getTypeId()](&system, &entity, dt);

在运行时,我将检查实体是否与系统兼容,但它是运行时信息。因此,所有函数指针必须在编译时为所有实体系统对注册,即使它们不兼容。这是我想要创建那些无操作功能的地方。

1 个答案:

答案 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_positionhas_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别名,将该应用程序转换为编译时布尔测试。

顺便说一下,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以及类似的其他更改(如果您的编译器不支持它)。