[libcxx-commits] [libcxx] [libc++][chrono] Implement LWG 4274: Allow chrono::hh_mm_ss to be constructed from durations with unsigned representations. (PR #209686)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Jul 14 23:01:23 PDT 2026
https://github.com/emmett2020 created https://github.com/llvm/llvm-project/pull/209686
[LWG4274: The chrono::hh_mm_ss constructor is ill-formed for unsigned durations](https://cplusplus.github.io/LWG/issue4274)
`chrono::hh_mm_ss` used `chrono::abs` for all durations. `chrono::abs` does not support unsigned representations. This made the constructor of `hh_mm_ss` fail for unsigned durations.
_**Example**_:
We're compiling a program with such piece of code:
```cpp
std::chrono::duration<unsigned int> dur{0};
std::chrono::hh_mm_ss<std::chrono::duration<unsigned int>> hms{dur};
(void)hms;
```
Then we'll get follow compilation error log:
```text
...
/home/utils/llvm-22.1.0/bin/../include/c++/v1/__chrono/hh_mm_ss.h:57:51: error: no matching function for call to 'abs'
57 | __h_(chrono::duration_cast<chrono::hours>(chrono::abs(__d))),
main.cpp:7:62: note: in instantiation of member function 'std::chrono::hh_mm_ss<std::chrono::duration<unsigned int>>::hh_mm_ss' requested here
7 | std::chrono::hh_mm_ss<std::chrono::duration<unsigned int>> hms{dur};
| ^
/home/utils/llvm-22.1.0/bin/../include/c++/v1/__chrono/time_point.h:132:78: note: candidate template ignored: requirement 'std::numeric_limits<unsigned int>::is_signed' was not
satisfied [with _Rep = unsigned int, _Period = std::ratio<1>]
132 | [[nodiscard]] inline _LIBCPP_HIDE_FROM_ABI constexpr duration<_Rep, _Period> abs(duration<_Rep, _Period> __d) {
```
**_Solution_**:
I updated the constructor to handle unsigned durations without calling `chrono::abs`. I also added tests for unsigned and custom signed representations.
**_Implementation details_**:
- Use `is_unsigned_v` instead of `numeric_limits<Rep>::is_signed`, which is used by `chrono::abs`. `numeric_limits<Rep>::is_signed` may return false when a custom `Rep` does not specialize `numeric_limits`. This may incorrectly treat a custom signed type as unsigned. `is_unsigned_v` provides a compile-time path for unsigned types, while the sign check keeps signed and custom types correct.
- The constructor calls `__abs_d` four times because each member is initialized separately. This should not affect runtime performance because the compiler should inline `__abs_d` and eliminate the repeated calculations. We could use another helper function or helper class to explicitly compute the absolute duration only once. However, this would add extra glue code, and the added complexity is not worthwhile I think.
Close #171328
>From bc598c40cb7bac1531c0a3c1b32c0c8536ac6cd1 Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Wed, 15 Jul 2026 12:59:35 +0800
Subject: [PATCH] [libc++] Implement LWG 4274
Allow chrono::hh_mm_ss to be constructed from durations with unsigned representations.
---
libcxx/docs/Status/Cxx26Issues.csv | 2 +-
libcxx/include/__chrono/hh_mm_ss.h | 20 ++++--
.../time.hms/time.hms.members/ctor.pass.cpp | 68 +++++++++++++++++++
3 files changed, 83 insertions(+), 7 deletions(-)
create mode 100644 libcxx/test/std/time/time.hms/time.hms.members/ctor.pass.cpp
diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index c203fa424556a..e37c05f035526 100644
--- a/libcxx/docs/Status/Cxx26Issues.csv
+++ b/libcxx/docs/Status/Cxx26Issues.csv
@@ -168,7 +168,7 @@
"`LWG4266 <https://wg21.link/LWG4266>`__","``layout_stride::mapping`` should treat empty mappings as exhaustive","2025-11 (Kona)","|Complete|","23","`#171325 <https://github.com/llvm/llvm-project/issues/171325>`__",""
"`LWG4269 <https://wg21.link/LWG4269>`__","``unique_copy`` passes arguments to its predicate backwards","2025-11 (Kona)","","","`#171326 <https://github.com/llvm/llvm-project/issues/171326>`__",""
"`LWG4272 <https://wg21.link/LWG4272>`__","For ``rank == 0``, ``layout_stride`` is atypically convertible","2025-11 (Kona)","","","`#171327 <https://github.com/llvm/llvm-project/issues/171327>`__",""
-"`LWG4274 <https://wg21.link/LWG4274>`__","The ``chrono::hh_mm_ss`` constructor is ill-formed for unsigned durations","2025-11 (Kona)","","","`#171328 <https://github.com/llvm/llvm-project/issues/171328>`__",""
+"`LWG4274 <https://wg21.link/LWG4274>`__","The ``chrono::hh_mm_ss`` constructor is ill-formed for unsigned durations","2025-11 (Kona)","|Complete|","24","`#171328 <https://github.com/llvm/llvm-project/issues/171328>`__",""
"`LWG4275 <https://wg21.link/LWG4275>`__","``std::dynamic_extent`` should also be defined in ``<mdspan>``","2025-11 (Kona)","|Complete|","21","`#171329 <https://github.com/llvm/llvm-project/issues/171329>`__",""
"`LWG4276 <https://wg21.link/LWG4276>`__","``front()`` and ``back()`` are not hardened for zero-length ``std::array``\s","2025-11 (Kona)","|Complete|","17","`#171330 <https://github.com/llvm/llvm-project/issues/171330>`__",""
"`LWG4280 <https://wg21.link/LWG4280>`__","``simd::partial_load`` uses undefined identifier ``T``","2025-11 (Kona)","","","`#171331 <https://github.com/llvm/llvm-project/issues/171331>`__",""
diff --git a/libcxx/include/__chrono/hh_mm_ss.h b/libcxx/include/__chrono/hh_mm_ss.h
index 746eba903d4f8..ddb2d055c16ca 100644
--- a/libcxx/include/__chrono/hh_mm_ss.h
+++ b/libcxx/include/__chrono/hh_mm_ss.h
@@ -11,9 +11,9 @@
#define _LIBCPP___CHRONO_HH_MM_SS_H
#include <__chrono/duration.h>
-#include <__chrono/time_point.h>
#include <__config>
#include <__type_traits/common_type.h>
+#include <__type_traits/is_unsigned.h>
#include <ratio>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -45,6 +45,14 @@ class hh_mm_ss {
return 0;
}
+ _LIBCPP_HIDE_FROM_ABI static constexpr _Duration __abs_d(_Duration __d, bool __is_neg) {
+ if constexpr (is_unsigned_v<typename _Duration::rep>) {
+ return __d;
+ } else {
+ return __is_neg ? -__d : __d;
+ }
+ }
+
public:
_LIBCPP_HIDE_FROM_ABI static unsigned constexpr fractional_width =
__width(__CommonType::period::den) < 19 ? __width(__CommonType::period::den) : 6u;
@@ -53,11 +61,11 @@ class hh_mm_ss {
_LIBCPP_HIDE_FROM_ABI constexpr hh_mm_ss() noexcept : hh_mm_ss{_Duration::zero()} {}
_LIBCPP_HIDE_FROM_ABI constexpr explicit hh_mm_ss(_Duration __d) noexcept
- : __is_neg_(__d < _Duration(0)),
- __h_(chrono::duration_cast<chrono::hours>(chrono::abs(__d))),
- __m_(chrono::duration_cast<chrono::minutes>(chrono::abs(__d) - hours())),
- __s_(chrono::duration_cast<chrono::seconds>(chrono::abs(__d) - hours() - minutes())),
- __f_(chrono::duration_cast<precision>(chrono::abs(__d) - hours() - minutes() - seconds())) {}
+ : __is_neg_(__d < _Duration::zero()),
+ __h_(chrono::duration_cast<chrono::hours>(__abs_d(__d, __is_neg_))),
+ __m_(chrono::duration_cast<chrono::minutes>(__abs_d(__d, __is_neg_) - hours())),
+ __s_(chrono::duration_cast<chrono::seconds>(__abs_d(__d, __is_neg_) - hours() - minutes())),
+ __f_(chrono::duration_cast<precision>(__abs_d(__d, __is_neg_) - hours() - minutes() - seconds())) {}
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool is_negative() const noexcept { return __is_neg_; }
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr chrono::hours hours() const noexcept { return __h_; }
diff --git a/libcxx/test/std/time/time.hms/time.hms.members/ctor.pass.cpp b/libcxx/test/std/time/time.hms/time.hms.members/ctor.pass.cpp
new file mode 100644
index 0000000000000..1d3a946ed8873
--- /dev/null
+++ b/libcxx/test/std/time/time.hms/time.hms.members/ctor.pass.cpp
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <chrono>
+//
+// template<class Duration>
+// class hh_mm_ss {
+// public:
+// constexpr explicit hh_mm_ss(Duration d);
+// };
+//
+// LWG4274: The hh_mm_ss constructor supports unsigned durations.
+
+#include <chrono>
+
+// A signed arithmetic-like type used as a custom duration representation.
+struct SignedRep {
+ long long value;
+
+ constexpr explicit SignedRep(long long v = 0) : value(v) {}
+ constexpr operator long long() const { return value; }
+
+ friend constexpr SignedRep operator-(SignedRep v) { return SignedRep{-v.value}; }
+ friend constexpr bool operator<(SignedRep lhs, SignedRep rhs) { return lhs.value < rhs.value; }
+};
+
+int main(int, char**) {
+ {
+ // Tests construction from a duration with an unsigned representation.
+ using Duration = std::chrono::duration<unsigned, std::milli>;
+
+ // 1 hour + 1 minute + 1 second + 1 millisecond
+ constexpr Duration d{3'661'001};
+ constexpr std::chrono::hh_mm_ss<Duration> hms{d};
+
+ static_assert(!hms.is_negative());
+ static_assert(hms.hours() == std::chrono::hours{1});
+ static_assert(hms.minutes() == std::chrono::minutes{1});
+ static_assert(hms.seconds() == std::chrono::seconds{1});
+ static_assert(hms.subseconds() == std::chrono::milliseconds{1});
+ static_assert(hms.to_duration() == std::chrono::milliseconds{3'661'001});
+ }
+
+ {
+ // Tests construction from a negative duration with a custom representation.
+ using Duration = std::chrono::duration<SignedRep>;
+
+ // 1 hour + 1 minute + 1 second
+ constexpr Duration d{SignedRep{-3'661}};
+ constexpr std::chrono::hh_mm_ss<Duration> hms{d};
+
+ static_assert(hms.is_negative());
+ static_assert(hms.hours() == std::chrono::hours{1});
+ static_assert(hms.minutes() == std::chrono::minutes{1});
+ static_assert(hms.seconds() == std::chrono::seconds{1});
+ static_assert(hms.subseconds() == std::chrono::seconds{0});
+ static_assert(hms.to_duration() == std::chrono::seconds{-3'661});
+ }
+
+ return 0;
+}
More information about the libcxx-commits
mailing list