[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:34:20 PDT 2026
https://github.com/KaroliShp created https://github.com/llvm/llvm-project/pull/230811
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.
>From 69c98a597c323f5d8aa5e477395d62616912cd1d Mon Sep 17 00:00:00 2001
From: Karolis <karolis.spukas at gmail.com>
Date: Sat, 10 Oct 2026 17:10:57 +0100
Subject: [PATCH] [libc++] Implement LWG4257: Stream insertion for chrono
local_time should be constrained
---
libcxx/docs/Status/Cxx26Issues.csv | 2 +-
libcxx/include/__chrono/ostream.h | 3 +
.../time.clock.local/ostream.pass.cpp | 31 +++++++
.../time.clock.local/ostream.verify.cpp | 89 -------------------
4 files changed, 35 insertions(+), 90 deletions(-)
delete mode 100644 libcxx/test/std/time/time.clock/time.clock.local/ostream.verify.cpp
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}};
-}
More information about the libcxx-commits
mailing list