如何测试是否存在模板函数特化

How to test if exists a template function specialization

我正在管理单位转换。告诉我们,我达到了我达到的状态。

我在不同单位之间转换的核心在于以下通用模板函数:

template <class SrcUnit, class TgtUnit> extern
double convert(double val);

此函数的目标是将以 SrcUnit 类型单位表示的物理量值转换为另一个以 TgtUnit.

类型单位表示的物理量值

我有一个名为 Quantity<Unit> 的 class,它管理值及其统一性,这个 class 试图提供类型安全和自动转换。例如,我导出以下构造函数:

  template <class SrcUnit>
  Quantity(const Quantity<SrcUnit> & q)
    : unit(UnitName::get_instance())
  {
    check_physical_units(q); // verify that the physical magnitudes are the same
    value = convert<SrcUnit, UnitName>(q.value); // <<- here the conversion is done
    check_value(); // check if the value is between the allowed interval
  }

我在转换完成的地方导出其他东西。

因此,当有人希望管理一个新单位时,她会指定一个新的 Unit 派生 class。我通过在宏中放入新 class 的所有需要​​的规范来实现这一点。现在,该用户负责编写转换函数。也就是写两个convert()模板的特化。例如,假设您有一个名为“Kilometerand you wish to specify a new unit calledMile”的单位。在这种情况下,您可以这样做:

Declare_Unit(Mile, "mi", "English unit of length", Distance,
         0, numeric_limits<double>::max()); // macro instantiating a new Unit class
template <> double convert<Kilometer, Mile>(double val) { return val/1609.344; }
template <> double convert<Mile, Kilometer>(double val) { return 1609.344*val; }

现在,如果用户忘记编写转换函数会怎样?那么,在这种情况下,链接器将失败,因为它找不到 convert() 特化。

现在我的问题。

虽然我认为链接器错误作为向用户报告丢失的 convert() 的行为是可以接受的,但我想测试我的编译时间是否存在 convert() 专业化。所以我的问题是我怎样才能做到这一点?我猜是通过在每次调用 convert() 之前放置的 static_assert 来测试专业化是否已知。但是要怎么做呢?

PS: 另外,如果有人能给我推荐一本关于 C++ 元编程的好书,那对我来说会非常有用。

你可以通过在主函数模板中放置一个 static_assert 来做到这一点,并使用一个小技巧来确保你的程序不是错误的,从 无耻地偷来的:

template <typename...> struct always_false : std::false_type {};

template<typename T, typename U> double convert(double) {
  static_assert(always_false<T, U>::value, "No specialization exists!");
}

template <> double convert<Kilometer, Mile>(double val) { return val/1609.344; }
template <> double convert<Mile, Kilometer>(double val) { return 1609.344*val; }

Live Demo

您不得向编译器承诺 SrcUnit/TgtUnit 的每种可能组合都存在转换:

template <class SrcUnit, class TgtUnit>
double convert(double val);

相反,您应该这样做,删除通用转换器:

template <class SrcUnit, class TgtUnit>
double convert(double val) = delete;

对于您提供的每一次转换 - 您应该在 header file/files:

中转发声明
template <>
double convert<Mille,Km>(double val);
template <>
double convert<Milliseconds,Hours>(double val);

因此,通过这种方式,编译器将知道提供了什么,没有提供什么


旁注:您的原始解决方案也可能有违反单一定义规则的危险。链接器无法检测您是否错误地提供了一种转换的两种不同实现。

也许这会有所帮助。

template<typename Src, typename Tgt, typename Exists = void>
double convert (double val) {
    static_assert(not std::is_void<Exists>::value, " err msg");
}

template <> double convert<Kilometer, Mile>(double val) { return val/1609.344; }

我会用标签来解决这个问题。

template<class T>struct tag_t{constexpr tag_t(){}; using type=T;};
template<class T>constexpr tag_t<T> tag{};

这让您可以将类型作为函数参数传递。

现在,我们不再使用特化之类的方法,而是调用一个函数。

我们将使用比 convert 更好的名称,例如 unit_convert

namespace my_ns {
  Declare_Unit(Mile, "mi", "English unit of length", Distance,
     0, numeric_limits<double>::max()); // macro instantiating a new Unit 
  inline double unit_convert( tag_t<Meter>, tag_t<Mile>, double val ) {
    return val/1609.344
  }
  inline double unit_convert( tag_t<Mile>, tag_t<Meter>, double val ) {
    return val*1609.344
  }
}

接下来我们写一个 trait,说明如果 unit_convert( tag<A>, tag<B>, double ) 存在:

namespace details {
  template<template<class...>class Z, class=void, class...Ts>
  struct can_apply:std::false_type{};
  template<template<class...>class Z,  class...Ts>
  struct can_apply<Z, std::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...Args>
using convert_unit_result = decltype( convert_unit( std::declval<Args>()... ) );

template<class SourceUnit, class DestUnit>
using can_convert_units = can_apply< convert_unit_result, tag_t<SourceUnit>, tag_t<DestUnit>, double >;

现在 can_convert_units<Mile,Meter>std::true_type,而 can_conver_units<Chicken, Egg>std::false_type(当然,假设鸡和蛋是不可转换单位的类型)。

作为奖励,此技术允许您将单元放在单独的名称空间中,并在那里定义它们的转换函数。如果 MileMeter 都试图定义它们之间的转换,那么当您尝试转换时会出错(不明确)。转换函数可以在 任一个 命名空间中,不能同时在两者中。