]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Make chrono::hh_mm_ss more compact
authorJonathan Wakely <jwakely@redhat.com>
Mon, 21 Nov 2022 16:44:50 +0000 (16:44 +0000)
committerJonathan Wakely <jwakely@redhat.com>
Tue, 6 Dec 2022 21:38:46 +0000 (21:38 +0000)
This uses a single byte for the minutes and seconds members, and places
the bool member next to those single bytes. This means we do not need 40
bytes to store a time that can fit in a single 8-byte integer.

When there is no subsecond precision we can do away with the _M_ss
member altogether. If the subsecond precision is coarse enough, we can
use a smaller representation for _M_ss, e.g. hh_mm_ss<milliseconds> only
needs uint_least32_t for _M_ss, and hh_mm_ss<duration<long, ratio<1,10>>
and hh_mm_ss<duration<int8_t, nano>> only need a single byte. In the
latter case the type can only ever represent up to 255ns anyway, so we
don't need a larger representation type (in such cases, we could even
remove the _M_h, _M_m and _M_s members, but it's a very unlikely
scenario that isn't worth optimizing for).

Except for specializations with a floating-point rep or using higher
precision than nanoseconds, hh_mm_ss should now fit in 16 bytes, or even
12 bytes for x86-32 where alignof(long long) == 4.

libstdc++-v3/ChangeLog:

* include/std/chrono (chrono::hh_mm_ss): Do not use 64-bit
representations for all four duration members. Reorder members.
(hh_mm_ss::hh_mm_ss()): Define as defaulted.
(hh_mm_ss::hh_mm_ss(Duration)): Delegate to a new private
constructor, instead of calling chrono::abs repeatedly.
* testsuite/std/time/hh_mm_ss/1.cc: Check floating-point
representations. Check default constructor. Check sizes.

libstdc++-v3/include/std/chrono
libstdc++-v3/testsuite/std/time/hh_mm_ss/1.cc

index 2468023f6c5eec242cef185dd54c387a176c8841..8d73d98e4cb91450166461ec810b3694827c2fb4 100644 (file)
@@ -41,6 +41,7 @@
 #include <bits/chrono.h>
 
 #if __cplusplus >= 202002L
+# include <bit>
 # include <sstream>
 # include <string>
 # include <vector>
@@ -2262,6 +2263,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
     // HH_MM_SS
 
+    /// @cond undocumented
     namespace __detail
     {
       consteval long long
@@ -2273,22 +2275,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        return __r;
       }
     }
+    /// @endcond
 
+    /** Utility for splitting a duration into hours, minutes, and seconds
+     *
+     * This is a convenience type that provides accessors for the constituent
+     * parts (hours, minutes, seconds and subseconds) of a duration.
+     *
+     * @since C++20
+     */
     template<typename _Duration>
       class hh_mm_ss
       {
+       static_assert( __is_duration<_Duration>::value );
+
       private:
-       static constexpr int
+       static consteval int
        _S_fractional_width()
        {
-         int __multiplicity_2 = 0;
-         int __multiplicity_5 = 0;
          auto __den = _Duration::period::den;
-         while ((__den % 2) == 0)
-           {
-             ++__multiplicity_2;
-             __den /= 2;
-           }
+         const int __multiplicity_2 = std::__countr_zero((uintmax_t)__den);
+         __den >>= __multiplicity_2;
+         int __multiplicity_5 = 0;
          while ((__den % 5) == 0)
            {
              ++__multiplicity_5;
@@ -2304,6 +2312,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          return __width;
        }
 
+       constexpr
+       hh_mm_ss(_Duration __d, bool __is_neg) noexcept
+       : _M_h (duration_cast<chrono::hours>(__d)),
+         _M_m (duration_cast<chrono::minutes>(__d - hours())),
+         _M_s (duration_cast<chrono::seconds>(__d - hours() - minutes())),
+         _M_is_neg(__is_neg)
+       {
+         auto __ss = __d - hours() - minutes() - seconds();
+         if constexpr (treat_as_floating_point_v<typename precision::rep>)
+           _M_ss._M_r = __ss.count();
+         else if constexpr (precision::period::den != 1)
+           _M_ss._M_r = duration_cast<precision>(__ss).count();
+       }
+
       public:
        static constexpr unsigned fractional_width = {_S_fractional_width()};
 
@@ -2312,28 +2334,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                                   chrono::seconds::rep>,
                     ratio<1, __detail::__pow10(fractional_width)>>;
 
-       constexpr
-       hh_mm_ss() noexcept
-       : hh_mm_ss{_Duration::zero()}
-       { }
+       constexpr hh_mm_ss() noexcept = default;
 
        constexpr explicit
        hh_mm_ss(_Duration __d) noexcept
-       : _M_is_neg (__d < _Duration::zero()),
-         _M_h (duration_cast<chrono::hours>(abs(__d))),
-         _M_m (duration_cast<chrono::minutes>(abs(__d) - hours())),
-         _M_s (duration_cast<chrono::seconds>(abs(__d) - hours() - minutes()))
-       {
-         if constexpr (treat_as_floating_point_v<typename precision::rep>)
-           _M_ss = abs(__d) - hours() - minutes() - seconds();
-         else
-           _M_ss = duration_cast<precision>(abs(__d) - hours()
-                                            - minutes() - seconds());
-       }
+       : hh_mm_ss(chrono::abs(__d), __d < _Duration::zero())
+       { }
 
        constexpr bool
        is_negative() const noexcept
-       { return _M_is_neg; }
+       {
+         if constexpr (!__is_unsigned)
+           return _M_is_neg;
+         else
+           return false;
+       }
 
        constexpr chrono::hours
        hours() const noexcept
@@ -2349,7 +2364,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
        constexpr precision
        subseconds() const noexcept
-       { return _M_ss; }
+       { return static_cast<precision>(_M_ss); }
 
        constexpr explicit
        operator precision() const noexcept
@@ -2358,20 +2373,82 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        constexpr precision
        to_duration() const noexcept
        {
-         if (_M_is_neg)
-           return -(_M_h + _M_m + _M_s + _M_ss);
-         else
-           return _M_h + _M_m + _M_s + _M_ss;
+         if constexpr (!__is_unsigned)
+           if (_M_is_neg)
+             return -(_M_h + _M_m + _M_s + subseconds());
+         return _M_h + _M_m + _M_s + subseconds();
        }
 
        // TODO: Implement operator<<.
 
       private:
-       bool _M_is_neg;
-       chrono::hours _M_h;
-       chrono::minutes _M_m;
-       chrono::seconds _M_s;
-       precision _M_ss;
+       static constexpr bool __is_unsigned
+         = __and_v<is_integral<typename _Duration::rep>,
+                   is_unsigned<typename _Duration::rep>>;
+
+       template<typename _Ratio>
+         using __byte_duration = duration<unsigned char, _Ratio>;
+
+       // The type of the _M_ss member that holds the subsecond precision.
+       template<typename _Dur>
+         struct __subseconds
+         {
+           typename _Dur::rep _M_r{};
+
+           constexpr explicit
+           operator _Dur() const noexcept
+           { return _Dur(_M_r); }
+         };
+
+       // An empty class if this precision doesn't need subseconds.
+       template<typename _Rep>
+         requires (!treat_as_floating_point_v<_Rep>)
+         struct __subseconds<duration<_Rep, ratio<1>>>
+         {
+           constexpr explicit
+           operator duration<_Rep, ratio<1>>() const noexcept
+           { return {}; }
+         };
+
+       // True if the maximum constructor argument can be represented in _Tp.
+       template<typename _Tp>
+         static constexpr bool __fits
+           = duration_values<typename _Duration::rep>::max()
+               <= duration_values<_Tp>::max();
+
+       template<typename _Rep, typename _Period>
+         requires (!treat_as_floating_point_v<_Rep>)
+           && ratio_less_v<_Period, ratio<1, 1>>
+           && (ratio_greater_equal_v<_Period, ratio<1, 250>>
+                 || __fits<unsigned char>)
+         struct __subseconds<duration<_Rep, _Period>>
+         {
+           unsigned char _M_r{};
+
+           constexpr explicit
+           operator duration<_Rep, _Period>() const noexcept
+           { return duration<_Rep, _Period>(_M_r); }
+         };
+
+       template<typename _Rep, typename _Period>
+         requires (!treat_as_floating_point_v<_Rep>)
+           && ratio_less_v<_Period, ratio<1, 250>>
+           && (ratio_greater_equal_v<_Period, ratio<1, 4'000'000'000>>
+                 || __fits<uint_least32_t>)
+         struct __subseconds<duration<_Rep, _Period>>
+         {
+           uint_least32_t _M_r{};
+
+           constexpr explicit
+           operator duration<_Rep, _Period>() const noexcept
+           { return duration<_Rep, _Period>(_M_r); }
+         };
+
+       chrono::hours               _M_h{};
+       __byte_duration<ratio<60>>  _M_m{};
+       __byte_duration<ratio<1>>   _M_s{};
+       bool                        _M_is_neg{};
+       __subseconds<precision>     _M_ss{};
       };
 
     // 12/24 HOURS FUNCTIONS
index 31dd1b2a8f31995e8b1ea6958a61f8e731c6aefa..3f8a838c47797476b161da996ab2e9a502cd4e2e 100644 (file)
@@ -59,5 +59,59 @@ constexpr_hh_mm_ss()
 
   static_assert(seconds{hh_mm_ss{100min}} == 100min);
 
-  // TODO: treat_as_floating_point_v
+  // treat_as_floating_point_v
+  using fseconds = duration<double, ratio<1>>;
+  constexpr hh_mm_ss<fseconds> fsec{0x123.0004p5s};
+  static_assert(std::is_same_v<hh_mm_ss<fseconds>::precision, fseconds>);
+  static_assert(fsec.hours() == 2h);
+  static_assert(fsec.minutes() == 35min);
+  static_assert(fsec.seconds() == 12s);
+  static_assert(fsec.subseconds() == 0x.0004p5s);
+  static_assert(!fsec.is_negative());
+  static_assert(fsec.to_duration() == 0x123.0004p5s);
+
+  using fminutes = duration<double, ratio<60>>;
+  constexpr hh_mm_ss<fminutes> fmin{-0x1.23p4min};
+  static_assert(std::is_same_v<hh_mm_ss<fminutes>::precision, fseconds>);
+  static_assert(fmin.hours() == 0h);
+  static_assert(fmin.minutes() == 18min);
+  static_assert(fmin.seconds() == 11s);
+  static_assert(fmin.subseconds() == 0.25s);
+  static_assert(fmin.is_negative());
+  static_assert(fmin.to_duration() == -0x1.23p4min);
+}
+
+constexpr void
+default_construction()
+{
+  using namespace std::chrono;
+
+  constexpr hh_mm_ss<seconds> s1;
+  static_assert(s1.to_duration() == s1.to_duration().zero());
+  constexpr hh_mm_ss<duration<char>> s2;
+  static_assert(s2.to_duration() == s2.to_duration().zero());
+  constexpr hh_mm_ss<duration<int, std::centi>> s3;
+  static_assert(s3.to_duration() == s3.to_duration().zero());
+  constexpr hh_mm_ss<duration<long long, std::femto>> s4;
+  static_assert(s4.to_duration() == s4.to_duration().zero());
+  constexpr hh_mm_ss<duration<double>> s5;
+  static_assert(s5.to_duration() == s5.to_duration().zero());
+}
+
+constexpr void
+size()
+{
+  using namespace std::chrono;
+
+  struct S0 { long long h; char m; char s; bool neg; };
+  static_assert(sizeof(hh_mm_ss<seconds>) == sizeof(S0));
+  struct S1 { long long h; char m; char s; bool neg; char ss; };
+  static_assert(sizeof(hh_mm_ss<duration<int, std::centi>>) == sizeof(S1));
+  struct S2 { long long h; char m, s; bool neg; int ss; };
+  static_assert(sizeof(hh_mm_ss<duration<int, std::milli>>) == sizeof(S2));
+  static_assert(sizeof(hh_mm_ss<duration<int, std::pico>>) == sizeof(S2));
+  struct S3 { long long h; char m, s; bool neg; long long ss; };
+  static_assert(sizeof(hh_mm_ss<duration<long long, std::pico>>) == sizeof(S3));
+  struct S4 { long long h; char m, s; bool neg; double ss; };
+  static_assert(sizeof(hh_mm_ss<duration<double, std::micro>>) == sizeof(S4));
 }