[libcxx-commits] [libcxx] [libc++] Implement LWG4257: Stream insertion for chrono local_time should be constrained (PR #230811)

via libcxx-commits libcxx-commits at lists.llvm.org
Sat Oct 10 09:35:00 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Karolishp (KaroliShp)

<details>
<summary>Changes</summary>

LWG4257 adds a constraint to `chrono::local_time` stream insertion.

I replaced `ostream.verify.cpp` tests with static asserts in `ostream.pass.cpp`. The new tests mirror all cases from `ostream.verify.cpp` and `sys_time.ostream.pass.cpp`. `ostream.verify.cpp` is now redundant, so I deleted it.

Fixes #<!-- -->171322.

## AI Disclosure

AI was used to review the changes before putting this PR up.

---
Full diff: https://github.com/llvm/llvm-project/pull/230811.diff


4 Files Affected:

- (modified) libcxx/docs/Status/Cxx26Issues.csv (+1-1) 
- (modified) libcxx/include/__chrono/ostream.h (+3) 
- (modified) libcxx/test/std/time/time.clock/time.clock.local/ostream.pass.cpp (+31) 
- (removed) libcxx/test/std/time/time.clock/time.clock.local/ostream.verify.cpp (-89) 


``````````diff
diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index 7cb257a49c30f..d580b47ab1428 100644
--- a/libcxx/docs/Status/Cxx26Issues.csv
+++ b/libcxx/docs/Status/Cxx26Issues.csv
@@ -162,7 +162,7 @@
 "`LWG4253 <https://wg21.link/LWG4253>`__","``basic_const_iterator`` should provide ``iterator_type``","2025-11 (Kona)","","","`#171319 <https://github.com/llvm/llvm-project/issues/171319>`__",""
 "`LWG4255 <https://wg21.link/LWG4255>`__","``move_only_function`` constructor should recognize empty ``copyable_function``\s","2025-11 (Kona)","","","`#171320 <https://github.com/llvm/llvm-project/issues/171320>`__",""
 "`LWG4256 <https://wg21.link/LWG4256>`__","Incorrect constrains for ``function_ref`` constructors from ``nontype_t``","2025-11 (Kona)","|Complete|","24","`#171321 <https://github.com/llvm/llvm-project/issues/171321>`__",""
-"`LWG4257 <https://wg21.link/LWG4257>`__","Stream insertion for ``chrono::local_time`` should be constrained","2025-11 (Kona)","","","`#171322 <https://github.com/llvm/llvm-project/issues/171322>`__",""
+"`LWG4257 <https://wg21.link/LWG4257>`__","Stream insertion for ``chrono::local_time`` should be constrained","2025-11 (Kona)","|Complete|","24","`#171322 <https://github.com/llvm/llvm-project/issues/171322>`__",""
 "`LWG4260 <https://wg21.link/LWG4260>`__","Query objects must be default constructible","2025-11 (Kona)","","","`#171323 <https://github.com/llvm/llvm-project/issues/171323>`__",""
 "`LWG4265 <https://wg21.link/LWG4265>`__","``std::midpoint`` should not accept ``const bool``","2025-11 (Kona)","|Complete|","22","`#171324 <https://github.com/llvm/llvm-project/issues/171324>`__",""
 "`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>`__",""
diff --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h
index 7a01b186780cb..e51efb130c0ea 100644
--- a/libcxx/include/__chrono/ostream.h
+++ b/libcxx/include/__chrono/ostream.h
@@ -95,6 +95,9 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const file_time<_Duration> __tp
 }
 
 template <class _CharT, class _Traits, class _Duration>
+  requires(requires(basic_ostream<_CharT, _Traits>& __os, const local_time<_Duration> __tp) {
+    __os << sys_time<_Duration>{__tp.time_since_epoch()};
+  })
 _LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
 operator<<(basic_ostream<_CharT, _Traits>& __os, const local_time<_Duration> __tp) {
   return __os << sys_time<_Duration>{__tp.time_since_epoch()};
diff --git a/libcxx/test/std/time/time.clock/time.clock.local/ostream.pass.cpp b/libcxx/test/std/time/time.clock/time.clock.local/ostream.pass.cpp
index 6ec63a14fbbd3..0114f572a0498 100644
--- a/libcxx/test/std/time/time.clock/time.clock.local/ostream.pass.cpp
+++ b/libcxx/test/std/time/time.clock/time.clock.local/ostream.pass.cpp
@@ -28,6 +28,7 @@
 #include <cassert>
 #include <ratio>
 #include <sstream>
+#include <type_traits>
 
 #include "make_string.h"
 #include "platform_support.h" // locale name macros
@@ -215,8 +216,38 @@ static void test_ja_JP() {
              std::chrono::duration<long long, std::ratio<1, 100>>{12'345'678'9010}}) == SV("2009-02-13 23:31:30.10"));
 }
 
+template <class CharT, class T>
+concept is_ostreamable = requires(std::basic_ostream<CharT>& os, T const& val) {
+  { os << val };
+};
+
 template <class CharT>
 static void test() {
+  // Test local_time's constraints:
+  //   os << sys_time<Duration>{lt.time_since_epoch()} is a valid expression.
+  static_assert(is_ostreamable<CharT, std::chrono::local_seconds>);
+  static_assert(is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<long long>>>);
+
+  // floating-point types
+  static_assert(!is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<float>>>);
+  static_assert(!is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<double>>>);
+  static_assert(!is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<long double>>>);
+
+  // duration > day
+  static_assert(!is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<int, std::ratio<86401>>>>);
+  static_assert(!is_ostreamable<CharT, std::chrono::local_time<std::chrono::months>>);
+  static_assert(!is_ostreamable<CharT, std::chrono::local_time<std::chrono::years>>);
+
+  // multiple of days are considered days
+  static_assert(is_ostreamable<CharT, std::chrono::local_days>);
+  static_assert(is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<int, std::ratio<3 * 86400>>>>);
+  static_assert(is_ostreamable<CharT, std::chrono::local_time<std::chrono::weeks>>);
+
+  // a different rep does not matter
+  using rep = std::conditional_t<std::is_same_v<std::chrono::days::rep, int>, long, int>;
+  static_assert(is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<rep, std::ratio<86400>>>>);
+  static_assert(is_ostreamable<CharT, std::chrono::local_time<std::chrono::duration<rep, std::ratio<20 * 86400>>>>);
+
   test_c<CharT>();
   test_fr_FR<CharT>();
   test_ja_JP<CharT>();
diff --git a/libcxx/test/std/time/time.clock/time.clock.local/ostream.verify.cpp b/libcxx/test/std/time/time.clock/time.clock.local/ostream.verify.cpp
deleted file mode 100644
index aa348b06947aa..0000000000000
--- a/libcxx/test/std/time/time.clock/time.clock.local/ostream.verify.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-// UNSUPPORTED: no-localization
-// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
-
-// TODO FMT This test should not require std::to_chars(floating-point)
-// XFAIL: availability-fp_to_chars-missing
-
-// <chrono>
-
-// class system_clock;
-
-// template<class charT, class traits, class Duration>
-//   basic_ostream<charT, traits>&
-//     operator<<(basic_ostream<charT, traits>& os, const local_time<Duration>& tp);
-
-// The function uses the system_clock which has two overloads
-
-// template<class charT, class traits, class Duration>
-//   basic_ostream<charT, traits>&
-//     operator<<(basic_ostream<charT, traits>& os, const sys_time<Duration>& tp);
-// Constraints: treat_as_floating_point_v<typename Duration::rep> is false, and Duration{1} < days{1} is true.
-
-// template<class charT, class traits>
-//   basic_ostream<charT, traits>&
-//     operator<<(basic_ostream<charT, traits>& os, const sys_days& dp);
-
-// Note local_time's operator<< is specified a
-//   Effects: os << sys_time<Duration>{lt.time_since_epoch()};
-// since it uses Effects and not Effects: Equivalent to the constrains of
-// sys_time do not apply to this operator. This means it's not possible to use
-// a SFINAE test.
-
-#include <chrono>
-#include <ratio>
-#include <sstream>
-#include <type_traits>
-
-void test() {
-  std::stringstream sstr;
-
-  // floating-point values
-
-  sstr << // expected-error@*:* {{invalid operands to binary expression}}
-      std::chrono::local_time<std::chrono::duration<float, std::ratio<1, 1>>>{
-          std::chrono::duration<float, std::ratio<1, 1>>{0}};
-
-  sstr << // expected-error@*:* {{invalid operands to binary expression}}
-      std::chrono::local_time<std::chrono::duration<double, std::ratio<1, 1>>>{
-          std::chrono::duration<double, std::ratio<1, 1>>{0}};
-
-  sstr << // expected-error@*:* {{invalid operands to binary expression}}
-      std::chrono::local_time<std::chrono::duration<long double, std::ratio<1, 1>>>{
-          std::chrono::duration<long double, std::ratio<1, 1>>{0}};
-
-  // duration >= day
-
-  sstr << // valid since day has its own formatter
-      std::chrono::local_days{std::chrono::days{0}};
-
-  using rep = std::conditional_t<std::is_same_v<std::chrono::days::rep, int>, long, int>;
-  sstr << // a different rep does not matter,
-      std::chrono::local_time<std::chrono::duration<rep, std::ratio<86400>>>{
-          std::chrono::duration<rep, std::ratio<86400>>{0}};
-
-  sstr << // expected-error@*:* {{invalid operands to binary expression}}
-      std::chrono::local_time<std::chrono::duration<typename std::chrono::days::rep, std::ratio<86401>>>{
-          std::chrono::duration<typename std::chrono::days::rep, std::ratio<86401>>{0}};
-
-  sstr << // These are considered days.
-      std::chrono::local_time<std::chrono::weeks>{std::chrono::weeks{3}};
-
-  sstr << // These too.
-      std::chrono::local_time<std::chrono::duration<rep, std::ratio<20 * 86400>>>{
-          std::chrono::duration<rep, std::ratio<20 * 86400>>{0}};
-
-  sstr << // expected-error@*:* {{invalid operands to binary expression}}
-      std::chrono::local_time<std::chrono::months>{std::chrono::months{0}};
-
-  sstr << // expected-error@*:* {{invalid operands to binary expression}}
-      std::chrono::local_time<std::chrono::years>{std::chrono::years{0}};
-}

``````````

</details>


https://github.com/llvm/llvm-project/pull/230811


More information about the libcxx-commits mailing list