现代C++神器之literals

Modern C++有很多理念极具启发性。我个人很欣赏std::chrono::duration的设计,比如说它的构造方式:

milliseconds t1(15);
hours t2 = hours(2);
auto t3 = hours(10);
milliseconds t4(minutes(2));
milliseconds t5 = hours(1);

上面几种都是普通的初始化,后两个会转换不同格式的数据。但是呢:

minutes t6 = 14min;
hours t7 = 2h;

这又是什么?为什么编译通过?那两个后缀竟然合法?不仅如此:

auto t8 = 12s;
milliseconds t9 = 3min;

这两个也是合法的。

这些后缀就算是literals。它是C++11之后的特性,可以当它是操作符重载的扩展:

constexpr chrono::duration<long double, milli>
    operator""ms(long double __msecs)
    { return chrono::duration<long double, milli>{__msecs}; }

我们下面模仿chrono::duration类写一个length类,分别定义三种可以互转的单位类型:

  • inch
  • feet
  • yard

当然你完全可以用面向对象方法实现,不过都Modern C++了,我只就考虑模板了。首先创建模板类:

template <typename T, typename R> struct length {
  template <typename T2>
  constexpr explicit length(const T2 &rep) : val(static_cast<T>(rep)) {}
  T val;
//copy constructor ...
};

长话短说,T就是数据类型,R表示比例尺度。然后定义三种单位类型:

using inch = length<double, std::ratio<1, 36>>;
using feet = length<double, std::ratio<1, 3>>;
using yard = length<double, std::ratio<1, 1>>;

解释一下,以码为基准,3英尺一码,12英寸一英尺也就是36英寸一码。这些数据将被用在类型转换上。这种实现方式对我来说还很新颖,在看chrono::duration代码之前,从来没想过可以这么搞。接下来重载literals

constexpr inch operator""_inch(long double val) { return inch(val); }
constexpr feet operator""_feet(long double val) { return feet(val); }
constexpr yard operator""_yard(long double val) { return yard(val); }

到这里关键部分已经齐全,不过标准规定自定义literals前缀必须为下划线_。为了实现自动转型还需要定义一下拷贝复制函数:

//...
  template <typename T2, typename R2>
  constexpr length(const length<T2, R2> &d) : val(length_cast(d)) {}

  template <typename T2, typename R2>
  T length_cast(const length<T2, R2> &des) {
    return des.val * static_cast<double>(R::den) /
           static_cast<double>(R2::den);
  }
//...

这里乍一看比较奇怪的是R::den,其实就是提取ratio<int Num, int Den>Den的值。最后演示实际用法:

auto l0 = 3_inch;  // the unit of l0 is inch
auto l1 = 10_feet; // the unit of l1 is feet
inch l2 = 2_yard;  // the val of l2 is 72
feet l3 = 9_inch;  // the val of l3 is 0.75

不知道你怎么看,狭义地看,这个表达力已经超过大多数语言。实际上chrono::duration为了更方便,安全地处理类型,使用了更多模板技巧,比如这一坨:

template<typename _Rep2, typename = typename
        enable_if<is_convertible<_Rep2, rep>::value
      && (treat_as_floating_point<rep>::value
          || !treat_as_floating_point<_Rep2>::value)>::type>
  constexpr explicit duration(const _Rep2& __rep)
  : __r(static_cast<rep>(__rep)) { }

我觉得这完全就是折腾库开发者,讨好用户的行为。

现代C++黑魔法千奇百怪,具有启发性,也提供了很多新思路。不过要完全掌握,求知之路还很长。

2 thoughts on “现代C++神器之literals”

Leave a Reply

Your email address will not be published. Required fields are marked *