【问题标题】:How to test if exists a template function specialization如何测试是否存在模板函数特化
【发布时间】:2016-12-02 22:09:05
【问题描述】:

我正在管理单位转换。告诉我们,我达到了我实现这一目标的状态。

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

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

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

我有一个名为Quantity&lt;Unit&gt; 的类,它管理值及其单位,并且该类试图提供类型安全和自动转换。例如,我导出以下构造函数:

  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 派生类。我通过将新类的所有所需规范放入宏中来实现这一点。现在,这个用户有责任编写转换函数。也就是写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++ 元编程的好文章,那对我来说非常有用。

【问题讨论】:

  • 检查link。有作者正在使用元编程机器开发一个单位转换系统。总的来说,最好的 TMP 介绍恕我直言。
  • 这似乎是一个根本上糟糕的设计。所以你有N^2 转换功能?您必须分别实现 both KilometerMileMileKilometer 吗?
  • 如果物理学允许N^2 转换,那么是的@Barry。对于第二个问题,也是。请注意,有些情况下转换不是严格线性的;尽管这听起来很奇怪,但对于某些地质相关性来说就是这种情况,其中转换只能在经验有效的区间内有效。包容性,在某些情况下,只允许在一次意义上进行转换。无论如何,非常欢迎任何关于设计的建议
  • 我所说的N^2 的意思是,如果你有N 单位,那么每个单位都需要自己的转换函数到每个其他单位......所以你有N^2 不同的转换函数。那...不能很好地扩展。
  • 好的@Barry 我明白了。是的,这不能很好地扩展,但我相信没有逃脱。我检查过的其他一些系统使用矩阵转换,虽然它可能更简洁,但仍然是二次的。在我目前工作的领域中,经验公式和不同单位的使用令人印象深刻。问候

标签: c++ templates c++11 c++14 template-meta-programming


【解决方案1】:

您可以通过在主函数模板中放置一个static_assert 来做到这一点,并使用一个小技巧来确保您的程序没有格式错误,从here 无耻地窃取:

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

【讨论】:

  • static_assert(false,...); 格式不正确。
  • 您的初始 foo() 格式不正确,因为 static_assert() 不依赖于 T
  • @TartanLlama 这仍然不正确吗?
  • @Barry 这仍然不正确吗?
  • @101010 没关系:)
【解决方案2】:

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

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

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

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

对于您提供的每一次转换 - 您应该在头文件/文件中转发声明:

template <>
double convert<Mille,Km>(double val);
template <>
double convert<Milliseconds,Hours>(double val);

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


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

【讨论】:

    【解决方案3】:

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

    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
      }
    }
    

    接下来我们编写一个特征来说明unit_convert( tag&lt;A&gt;, tag&lt;B&gt;, 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&lt;Mile,Meter&gt;std::true_type,而can_conver_units&lt;Chicken, Egg&gt;std::false_type(假设 chicken 和 egg 是不可转换单位的类型,自然)。

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

    【讨论】:

      【解决方案4】:

      也许这会有所帮助。

      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; }
      

      【讨论】:

        猜你喜欢
        • 2021-09-04
        • 2011-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-24
        相关资源
        最近更新 更多