From 151d05039115a0be920afebdbc2a1e72b531fdac Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Sat, 30 Jul 2016 17:45:14 -0400 Subject: [PATCH] Introduce chrono_io.h --- chrono_io.html | 332 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 chrono_io.html diff --git a/chrono_io.html b/chrono_io.html new file mode 100644 index 0000000..3b398f4 --- /dev/null +++ b/chrono_io.html @@ -0,0 +1,332 @@ + + + + chrono_io + + + + + +
+
+
+Howard E. Hinnant
+2016-07-30
+
+
+

chrono_io

+ +

Contents

+ + + +

Introduction

+ +

+This is a small library which does nothing but add non-configurable, non-localizable +streaming to std::chrono::duration. The streaming operator lives in +namespace date for convenience purposes, and does not live in +namespace std::chrono as that would violate the standard. +

+ +

+The entire API of this library is: +

+ +
+namespace date
+{
+
+template <class CharT, class Traits, class Rep, class Period>
+std::basic_ostream<CharT, Traits>&
+operator<<(std::basic_ostream<CharT, Traits>& os,
+           const std::chrono::duration<Rep, Period>& d);
+
+}  // namespace date
+
+ +

+Just one signature for an entire library? Are you serious?! +

+ +
+

+Very serious. +

+

+Parsing is not included because there are a million ways to parse durations, and none +of them are universally agreed upon. And they are all easily implementable without +private access to std::chrono::duration just by parsing arithmetic types +and strings. +

+
+ +

+No formatting options?! +

+ +
+

+No. +

+

+The formatting provided here is based on an international SI standard. There is +no need for localization. If you need other formatting options for streaming +your durations that is easy enough for you to supply. What is needed here is +zero-effort streaming of chrono::durations without hassle. +Streaming chrono::durations is the biggest use of +.count() (a dangerous type cast), and that use should be handled by +a library such as this. +

+
+ +

Examples

+ +
+#include "chrono_io.h"
+#include <iostream>
+
+int
+main()
+{
+    using namespace date;
+    using namespace std::chrono;
+    auto t0 = steady_clock::now();
+    auto t1 = steady_clock::now();
+    std::cout << t1 - t0 << '\n';
+}
+
+ +

+With this library, you don't have to specify the duration type (and cast to it) in timing +situations like this. This will create whatever duration is native to +steady_clock and print out the value and units for that duration, +for example: +

+ +
+104ns
+
+ +

+Change steady_clock to system_clock (and if that clock has +different units on your platform), and the output automatically changes units for you: +

+ +
+auto t0 = system_clock::now();
+auto t1 = system_clock::now();
+std::cout << t1 - t0 << '\n';
+
+ +
+0µs
+
+ +

+And yes, it really does print out the Greek letter µ and not +u. When streaming to char-based streams it uses UTF-8 encoding +to represent the Unicode character 'MICRO SIGN' (U+00B5). Otherwise it uses UTF-16 or +UTF-32 based on the size of character the stream is using. +

+ +

+In general, if the duration tick period is the same type as one of the non-optional +std::ratio SI convenience typedefs (atto thru exa), +then the unit will use the internationally accepted symbol for the +metric prefix followed +by 's'. +

+ +

+In addition, if the duration tick period is ratio<60>, then the +printed unit is min. And if the duration tick period is +ratio<3600>, then the unit is h. +

+ +
+std::cout << 1s << '\n';
+std::cout << 2min << '\n';
+std::cout << 3h << '\n';
+
+ +

+Outputs: +

+ +
+1s
+2min
+3h
+
+ +

+Sometimes odd durations pop up which have no widely recognized names. For example +consider this code: +

+ +
+using frames = duration<int, ratio<1, 60>>;
+std::cout << 45ms + frames{5} << '\n';
+
+ +

+This sum can't be exactly represented as either milliseconds or frames (1/60 of a second). +The compiler figures out the coarsest unit which can exactly represent the sum of +any millisecond and any frame and auto-generates that unit to hold the sum. For this +example that duration will have a tick period of 1/3000 of a second. This library +puts that ratio inside of square brackets, and then appends 's': +

+ +
+385[1/3000]s
+
+ +

+If the durations tick period is a whole number of seconds (period::den == 1), +then the /1 is dropped inside of the square brackets. The following example +uses months from date.h. +

+ +
+std::cout << months{6} << '\n';
+
+ +
+6[2629746]s
+
+ +

+Trivia: 2,629,746s is the exact length of the average Gregorian month, which is also exactly +30.436875 24h days. +

+ +

Implementation Details

+ +

+Question: Why are there separate implementations for C++11 and C++14? +

+ +

+Answer: The units of a duration are always known at compile +time. Thus the string representation of the units is always known at compile time. This +is true even when the unit is of the form [num/den]s. But only C++14 has +the gravitas to do the complete string conversion at compile time. And now that I've +claimed that, I'm sure someone will come up with a C++11 that VS-2015 won't compile anyway. +So I need a run-time solution (based on std::basic_string<CharT>, +and a compile-time solution that currently only gcc and clang with -std=c++14 +can handle. +

+ +

+For example if you compile the following code with -std=c++14 and clang: +

+ +
+using frames = std::chrono::duration<int, std::ratio<1, 60>>;
+
+void
+test(std::ostream& os, std::chrono::milliseconds x, frames y)
+{
+    using namespace date;
+    os << x + y;
+}
+
+ +

+And then inspect the generated assembly, it contains this: +

+ +
+movabsq	$6714920027984703835, %rax ## imm = 0x5D303030332F315B
+
+ +

+Which is assembly-speak for "[1/3000]" all boiled down to one x86_64 instruction. The +'s' wouldn't quite fit within this one instruction and follows in a later +instruction. C++14 can do some amazing things at compile time! For C++11 I just +gave up and called std::to_string. +

+ +

Reference

+ +
+template <class CharT, class Traits, class Rep, class Period>
+std::basic_ostream<CharT, Traits>&
+operator<<(std::basic_ostream<CharT, Traits>& os,
+           const std::chrono::duration<Rep, Period>& d);
+
+ +
+

+Effects: Equivalent to: +

+
+os << d.count() << detail::get_units<CharT>(duration<Rep, typename Period::type>{});
+
+

+Where detail::get_units<CharT>() returns a null-terminated string of +CharT which depends only on Period::type as follows (let +period be the type Period::type): +

+ +
    +
  • If period is type std::atto, as, else
  • +
  • if period is type std::femto, fs, else
  • +
  • if period is type std::pico, ps, else
  • +
  • if period is type std::nano, ns, else
  • +
  • if period is type std::micro, µs (U+00B5), else
  • +
  • if period is type std::milli, ms, else
  • +
  • if period is type std::centi, cs, else
  • +
  • if period is type std::deci, ds, else
  • +
  • if period is type std::ratio<1>, s, else
  • +
  • if period is type std::deca, das, else
  • +
  • if period is type std::hecto, hs, else
  • +
  • if period is type std::kilo, ks, else
  • +
  • if period is type std::mega, Ms, else
  • +
  • if period is type std::giga, Gs, else
  • +
  • if period is type std::tera, Ts, else
  • +
  • if period is type std::peta, Ps, else
  • +
  • if period is type std::exa, Es, else
  • +
  • if period is type std::ratio<60>, min, else
  • +
  • if period is type std::ratio<3600>, h, else
  • +
  • if period::den == 1, [num]s, else
  • +
  • [num/den]s.
  • +
+ +

+In the list above the use of num and den refer to the +static data members of period which are converted to arrays of +CharT using a decimal conversion with no leading zeroes. +

+ +
+

+Returns: os. +

+
+
+ + +