Revised 2025-12-22 at 14:28:09 UTC
year is ambiguousSection: 30.12 [time.format], 30.13 [time.parse] Status: Tentatively Ready Submitter: Matt Stephanson Opened: 2022-11-18 Last modified: 2025-12-04
Priority: 3
View other active issues in [time.format].
View all other issues in [time.format].
View all issues with Tentatively Ready status.
Discussion:
An issue has been identified regarding the two-digit formatting of negative years according to Table [tab:time.format.spec] (30.12 [time.format]):
cout << format("{:%y} ", 1976y) // "76"
<< format("{:%y}", -1976y); // also "76"?
The relevant wording is
The last two decimal digits of the year. If the result is a single digit it is prefixed by
0. The modified command%Oyproduces the locale's alternative representation. The modified command%Eyproduces the locale's alternative representation of offset from%EC(year only).
MSVC STL treats the regular modified form symmetrically. Just as %Ey is the offset from
%EC, so %y is the offset from %C, which is itself "[t]he year divided by 100
using floored division." (emphasis added). Because -1976 is the 24th year of the -20th century,
the above code will print "76 24" using MSVC STL. However, many users expect, and
libc++
gives, a result based on the literal wording, "76 76".
%C and %y as the quotient and remainder
of floored division by 100.
Howard Hinnant, coauthor of the original 30.12 [time.format] wording in P0355 adds:
On the motivation for this design it is important to remember a few things:
POSIX
strftime/strptimedoesn't handle negative years in this department, so this is an opportunity for an extension in functionality.This is a formatting/parsing issue, as opposed to a computational issue. This means that human readability of the string syntax is the most important aspect. Computational simplicity takes a back seat (within reason).
%Ccan't be truncated division, otherwise the years [-99, -1] would map to the same century as the years [0, 99]. So floored division is a pretty easy and obvious solution.
%yis obvious for non-negative years: The last two decimal digits, ory % 100.This leaves how to represent negative years with
%y. I can think of 3 options:
Use the last two digits without negating: -1976 → 76.
Use the last two digits and negate it: -1976 → -76.
Use floored modulus arithmetic: -1976 → 24.
The algorithm to convert
I discounted solution 3 as not sufficiently obvious. If the output for -1976 was 23, the human reader wouldn't immediately know that this is off by 1. The reader is expecting the POSIX spec:%Cand%yinto a year is not important to the client because these are both strings, not integers. The client will do it withparse, not100*C + y.the last two digits of the year as a decimal number [00,99].
24 just doesn't cut it.
That leaves solution 1 or 2. I discounted solution 2 because having the negative in 2 places (the%Cand%y) seemed overly complicated and more error prone. The negative sign need only be in one place, and it has to be in%Cto prevent ambiguity. That leaves solution 1. I believe this is the solution for an extension of the POSIX spec to negative years with the property of least surprise to the client. The only surprise is in%C, not%y, and the surprise in%Cseems unavoidable.
[2022-11-30; Reflector poll]
Set priority to 3 after reflector poll.
A few votes for priority 2. Might need to go to LEWG.
Previous resolution [SUPERSEDED]:
This wording is relative to N4917.
[Drafting Note: Two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.]
Option A: This is Howard Hinnant's choice (3)
Modify 30.12 [time.format], Table [tab:time.format.spec] as indicated:
Table 102 — Meaning of conversion specifiers [tab:time.format.spec] Specifier Replacement […]%yThe last two decimal digits of the yearremainder after dividing the year by 100 using floored division.
If the result is a single digit it is prefixed by0.
The modified command%Oyproduces the locale's alternative representation. The
modified command%Eyproduces the locale's alternative representation of offset from
%EC(year only).[…]Modify 30.13 [time.parse], Table [tab:time.parse.spec] as indicated:
Table 103 — Meaning of parseflags [tab:time.parse.spec]Flag Parsed value […]%yThe last two decimal digits of the yearremainder after dividing the year by 100 using floored division.
If the century is not otherwise specified (e.g.
with%C), values in the range [69,99] are presumed to refer to the years 1969 to 1999,
and values in the range [00,68] are presumed to refer to the years 2000 to 2068. The
modified command%N yspecifies the maximum number of characters to read. If N is
not specified, the default is 2. Leading zeroes are permitted but not required. The
modified commands%Eyand%Oyinterpret the locale's alternative representation.[…]Option B: This is Howard Hinnant's choice (1)
Modify 30.12 [time.format], Table [tab:time.format.spec] as indicated:
Table 102 — Meaning of conversion specifiers [tab:time.format.spec] Specifier Replacement […]%yThe last two decimal digits of the year, regardless of the sign of the year.
If the result is a single digit it is prefixed by0.
The modified command%Oyproduces the locale's alternative representation. The
modified command%Eyproduces the locale's alternative representation of offset from
%EC(year only).
[Example ?:cout << format("{:%C %y}", -1976y);prints-20 76. — end example][…]Modify 30.13 [time.parse], Table [tab:time.parse.spec] as indicated:
Table 103 — Meaning of parseflags [tab:time.parse.spec]Flag Parsed value […]%yThe last two decimal digits of the year, regardless of the sign of the year.
If the century is not otherwise specified (e.g.
with%C), values in the range [69,99] are presumed to refer to the years 1969 to 1999,
and values in the range [00,68] are presumed to refer to the years 2000 to 2068. The
modified command%N yspecifies the maximum number of characters to read. If N is
not specified, the default is 2. Leading zeroes are permitted but not required. The
modified commands%Eyand%Oyinterpret the locale's alternative representation.
[Example ?:year y; istringstream{"-20 76"} >> parse("%3C %y", y);results in
y == -1976y. — end example][…]
[2025-10-17; Jonathan provides updated wording using Option B]
[2025-12-04; Reflector poll.]
Set status to Tentatively Ready after seven votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5014.
Modify 30.12 [time.format], Table [tab:time.format.spec] as indicated:
Table 133 — Meaning of conversion specifiers [tab:time.format.spec] Specifier Replacement […]%yThe last two decimal digits of the year, regardless of the sign of the year.
If the result is a single digit it is prefixed by0.
The modified command%Oyproduces the locale's alternative representation. The
modified command%Eyproduces the locale's alternative representation of offset from
%EC(year only).
[Example ?:cout << format("{:%C %y}", -1976y);prints-20 76. — end example][…]
Modify 30.13 [time.parse], Table [tab:time.parse.spec] as indicated:
Table 103 — Meaning of parseflags [tab:time.parse.spec]Flag Parsed value […]%yThe last two decimal digits of the year, regardless of the sign of the year.
If the century is not otherwise specified (e.g.
with%C), values in the range [69,99] are presumed to refer to the years 1969 to 1999,
and values in the range [00,68] are presumed to refer to the years 2000 to 2068. The
modified command%N yspecifies the maximum number of characters to read. If N is
not specified, the default is 2. Leading zeroes are permitted but not required. The
modified commands%Eyand%Oyinterpret the locale's alternative representation.
[Example ?:year y; istringstream{"-20 76"} >> parse("%3C %y", y);results in
y == -1976y. — end example][…]
std::basic_string on some platformsSection: 27.4.3.8.2 [string.find] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-05-05 Last modified: 2025-12-04
Priority: 3
View other active issues in [string.find].
View all other issues in [string.find].
View all issues with Tentatively Ready status.
Discussion:
P1148R0 respecified the searching functions of std::basic_string to return
corresponding string view type's npos member constant (equal to std::size_t(-1)), converted
to the string type S's member S::size_type, when the search fails. Before the change,
S::npos (equal to S::size_type(-1)) was returned on failure.
std::size_t isn't the widest unsigned integer type (e.g. on usual 32-bit
platforms), the return value can change. Because there can be an allocator with a wider size_type,
and when the basic_string type S uses such an allocator, S::size_type is specified to be that
type, which in turn makes S::size_type(std::size_t(-1)) not equal to S::size_type(-1).
Do we want to restore the old return values?
Previous resolution [SUPERSEDED]:
This wording is relative to N5008.
Modify 27.4.3.8.2 [string.find] as indicated:
template<class T> constexpr size_type find(const T& t, size_type pos = 0) const noexcept(see below); […] template<class T> constexpr size_type find_last_not_of(const T& t, size_type pos = npos) const noexcept(see below);-2- Constraints: […]
-3- Effects: LetGbe the name of the function. Equivalent to:basic_string_view<charT, traits> s = *this, sv = t;return s.G(sv, pos);if (auto result = s.G(sv, pos); result == size_t(-1)) return npos; else return result;
[2025-06-10, reflector discussion]
During reflector discussion of this issue there was a preference to adjust the
proposed wording to use s.npos instead of size_t(-1).
[2025-10-21; Reflector poll.]
Set priority to 3 after reflector poll.
[2025-12-04; Reflector poll.]
Set status to Tentatively Ready after five votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5008.
Modify 27.4.3.8.2 [string.find] as indicated:
template<class T> constexpr size_type find(const T& t, size_type pos = 0) const noexcept(see below); […] template<class T> constexpr size_type find_last_not_of(const T& t, size_type pos = npos) const noexcept(see below);-2- Constraints: […]
-3- Effects: LetGbe the name of the function. Equivalent to:basic_string_view<charT, traits> s = *this, sv = t;return s.G(sv, pos);if (auto result = s.G(sv, pos); result == s.npos) return npos; else return result;
unique_ptr<void>::operator* is not SFINAE-friendlySection: 20.3.1.3.5 [unique.ptr.single.observers] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-08-24 Last modified: 2025-12-04
Priority: 3
View other active issues in [unique.ptr.single.observers].
View all other issues in [unique.ptr.single.observers].
View all issues with Tentatively Ready status.
Discussion:
LWG 2762(i) added a conditional noexcept specification to unique_ptr::operator*
to make it consistent with shared_ptr::operator*:
constexpr add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>()));
This unexpectedly makes unique_ptr<void>::operator* no longer SFINAE-friendly,
for example:
#include <memory>
template<class T> concept dereferenceable = requires(T& t) { *t; };
static_assert( dereferenceable<int *>);
static_assert(!dereferenceable<void*>);
static_assert( dereferenceable<std::shared_ptr<int >>);
static_assert(!dereferenceable<std::shared_ptr<void>>);
static_assert( dereferenceable<std::unique_ptr<int >>);
static_assert( dereferenceable<std::unique_ptr<void>>); // hard error
Given that the standard intends for operator* of shared_ptr and unique_ptr to be
SFINAE-friendly based on 20.3.2.2.6 [util.smartptr.shared.obs], regardless of the value of
static_assert, it is reasonable to assume that there should be no hard error here.
Previous resolution [SUPERSEDED]:
This wording is relative to N5014.
Modify 20.3.1.3.5 [unique.ptr.single.observers] as indicated:
constexpr add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>())see below);-1- Mandates:
-2- Preconditions:reference_converts_from_temporary_v<add_lvalue_reference_t<T>, decltype(*declval<pointer>())>isfalse.get() != nullptristrue. -3- Returns:*get(). -?- Remarks:: The exception specification is equivalent to:!requires { *declval<pointer>(); } || requires { { *declval<pointer>() } noexcept; }
[2025-08-26; Reflector discussion]
During reflector triaging it had been pointed out that a better solution would be to constrain the
operator* directly. The proposed wording has been updated to that effect.
[2025-10-22; Reflector poll.]
Set priority to 3 after reflector poll.
[2025-12-04; Reflector poll.]
Set status to Tentatively Ready after eight votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5014.
Modify 20.3.1.3.5 [unique.ptr.single.observers] as indicated:
constexpr add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>()));-?- Constraints:
-1- Mandates:*declval<pointer>()is a well-formed expression.reference_converts_from_temporary_v<add_lvalue_reference_t<T>, decltype(*declval<pointer>())>isfalse. -2- Preconditions:get() != nullptristrue. -3- Returns:*get().
variant constructorSection: 22.6.3.2 [variant.ctor] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-11-07 Last modified: 2025-12-04
Priority: Not Prioritized
View other active issues in [variant.ctor].
View all other issues in [variant.ctor].
View all issues with Tentatively Ready status.
Discussion:
All variant constructors except the last one have a Throws: element
saying what they're allowed to throw.
This originates from an editorial pull request, where the submitter said:
"It looks like this defect is an artifact of a change between P0088R0 and P0088R1. Note how in R0 neither one of theemplaced_type_t/emplaced_index_t(as they were then called) +initializer_listconstructors have a throws clause. In R1 only one of them gained it."
Previous resolution [SUPERSEDED]:
This wording is relative to N5014.
Modify 22.6.3.2 [variant.ctor], as indicated:
template<size_t I, class U, class... Args> constexpr explicit variant(in_place_index_t<I>, initializer_list<U> il, Args&&... args);-35- Constraints:
- (35.1) —
Iis less thansizeof...(Types)and- (35.2) —
is_constructible_v<TI, initializer_list<U>&, Args...>istrue.-36- Effects: Direct-non-list-initializes the contained value of type
TIwithil, std::forward<Args>(args)....-37- Postconditions:
index()isI.-?- Throws: Any exception thrown by calling the selected constructor of
TI.-38- Remarks: If
TI’s selected constructor is a constexpr constructor, this constructor is a constexpr constructor.
[2025-11-11; Jonathan provides improved wording]
[2025-12-04; Reflector poll.]
Set status to Tentatively Ready after six votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5014.
Modify 22.6.3.2 [variant.ctor], as indicated:
constexpr variant() noexcept(see below);-2- Constraints:
is_default_constructible_v<T0>istrue.-3- Effects: Constructs a
variantholding a value-initialized value of typeT0.-4- Postconditions:
valueless_by_exception()isfalseandindex()is0.-5- Throws: Any exception thrown by the value-initialization of
T0.-6- Remarks: […]
constexpr variant(const variant&);-7- Effects: If
wholds a value, initializes thevariantto hold the same alternative aswand direct-initializes the contained value withGET<j>(w), wherejisw.index(). Otherwise, initializes thevariantto not hold a value.-8- Throws: Any exception thrown by
direct-initializating anythe initialization of the contained value.Tifor all i-9- Remarks: […]
constexpr variant(variant&&) noexcept(see below);-10- Constraints:
is_move_constructible_v<Ti>istruefor all i.-11- Effects: If
wholds a value, initializes thevariantto hold the same alternative aswand direct-initializes the contained value withGET<j>(std::move(w)), wherejisw.index(). Otherwise, initializes thevariantto not hold a value.-12- Throws: Any exception thrown by
move-constructing anythe initialization of the contained value.Tifor all i-13- Remarks: […]
template<class T> constexpr variant(T&&) noexcept(see below);-14- Let
Tjbe a type that is determined as follows: build an imaginary functionFUN(Ti)for each alternative typeTifor whichTi x[] = {std::forward<T>(t)};is well-formed for some invented variablex. The overloadFUN(Tj)selected by overload resolution for the expressionFUN(std::forward<T>(t))defines the alternativeTjwhich is the type of the contained value after construction.-15- Constraints: […]
-16- Effects: Initializes
*thisto hold the alternative typeTjand direct-non-list-initializes the contained value withstd::forward<T>(t).-17- Postconditions: […]
-18- Throws: Any exception thrown by the initialization of the
selected alternativecontained value.Tj-19- Remarks: […]
template<class T, class... Args> constexpr variant(in_place_type_t<T>, Args&&... args);-20- Constraints: […]
-21- Effects: Direct-non-list-initializes the contained value of type
Twithstd::forward<Args>(args)....-22- Postconditions: […]
-23- Throws: Any exception thrown by
the selected constructor ofthe initialization of the contained value.T-24- Remarks: […]
template<class T, class U, class... Args> constexpr variant(in_place_type_t<T>, initializer_list<U> li, Args&&... args);-25- Constraints: […]
-26- Effects: Direct-non-list-initializes the contained value of type
Twithil, std::forward<Args>(args)....-27- Postconditions: […]
-28- Throws: Any exception thrown by
the selected constructor ofthe initialization of the contained value.T-29- Remarks: […]
template<size_t I, class... Args> constexpr explicit variant(in_place_index_t<I>, Args&&... args);-30- Constraints:
- (30.1) —
Iis less thansizeof...(Types)and- (30.2) —
is_constructible_v<TI, Args...>istrue.-31- Effects: Direct-non-list-initializes the contained value of type
TIwithstd::forward<Args>(args)....-32- Postconditions:
index()isI.-33- Throws: Any exception thrown by
the selected constructor ofthe initialization of the contained value.Ti-34- Remarks: If
TI’s selected constructor is a constexpr constructor, this constructor is a constexpr constructor.template<size_t I, class U, class... Args> constexpr explicit variant(in_place_index_t<I>, initializer_list<U> il, Args&&... args);-35- Constraints:
- (35.1) —
Iis less thansizeof...(Types)and- (35.2) —
is_constructible_v<TI, initializer_list<U>&, Args...>istrue.-36- Effects: Direct-non-list-initializes the contained value of type
TIwithil, std::forward<Args>(args)....-37- Postconditions:
index()isI.-?- Throws: Any exception thrown by the initialization of the contained value.
-38- Remarks: If
TI’s selected constructor is a constexpr constructor, this constructor is a constexpr constructor.
hive::splice can throw bad_allocSection: 23.3.9.5 [hive.operations] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-11-07 Last modified: 2025-12-04
Priority: Not Prioritized
View other active issues in [hive.operations].
View all other issues in [hive.operations].
View all issues with Tentatively Ready status.
Discussion:
Moving blocks from the source hive to the destination hive might require
reallocating the array of pointers to blocks, so the Throws: element
should allow this.
[2025-12-04; Reflector poll.]
Set status to Tentatively Ready after seven votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5014.
Modify 23.3.9.5 [hive.operations], as indicated:
void splice(hive& x); void splice(hive&& x);-2- Preconditions:
get_allocator() == x.get_allocator()istrue.-3- Effects: If
addressof(x) == thisistrue, the behavior is erroneous and there are no effects. Otherwise, inserts the contents ofxinto*thisandxbecomes empty. Pointers and references to the moved elements ofxnow refer to those same elements but as members of*this. Iterators referring to the moved elements continue to refer to their elements, but they now behave as iterators into*this, not intox.-4- Throws:
length_errorif any ofx's active blocks are not within the bounds ofcurrent-limits, as well as any exceptions thrown by the allocator.-5- Complexity: Linear in the sum of all element blocks in
xplus all element blocks in*this.-6- Remarks: Reserved blocks in
xare not transferred into*this. Ifaddressof(x) == thisisfalse, invalidates the past-the-end iterator for bothxand*this.
operator decltype(auto)" is ill-formedSection: 21.3.5 [const.wrap.class] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-11-07 Last modified: 2025-12-04
Priority: Not Prioritized
View all other issues in [const.wrap.class].
View all issues with Tentatively Ready status.
Discussion:
Following the approval of CWG 1670 in Kona 2025, the
following declaration in class template constant_wrapper, 21.3.5 [const.wrap.class],
is ill-formed:
constexpr operator decltype(auto)() const noexcept { return value; }
[2025-12-04; Reflector poll.]
Set status to Tentatively Ready after seven votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5014.
Modify 21.3.5 [const.wrap.class], class template constant_wrapper synopsis, as indicated:
[…]
template<cw-fixed-value X, class>
struct constant_wrapper : cw-operators {
static constexpr const auto & value = X.data;
using type = constant_wrapper;
using value_type = typename decltype(X)::type;
[…]
constexpr operator decltype(autovalue)() const noexcept { return value; }
};
operator delete should be constexprSection: 17.6.3.4 [new.delete.placement] Status: Tentatively Ready Submitter: Jakub Jelinek Opened: 2025-11-18 Last modified: 2025-11-26
Priority: Not Prioritized
View other active issues in [new.delete.placement].
View all other issues in [new.delete.placement].
View all issues with Tentatively Ready status.
Discussion:
The P2747R2 paper made placement operator new constexpr. At that time constexpr exceptions weren't in C++26, so that was all that was needed. But later on when P3068R5 was voted in, the P2747R2 changes look insufficient. The problem is that when you throw from a constructor during operator new, it invokes placement operator delete. And P2747R2 didn't touch that.
This makes it impossible to handle an exception thrown by a placement new-expression during constant evaluation.
[2025-11-26; Reflector poll.]
Set status to Tentatively Ready after six votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5014.
Modify 17.6.2 [new.syn] as indicated:
constexpr void* operator new (std::size_t size, void* ptr) noexcept; constexpr void* operator new[](std::size_t size, void* ptr) noexcept; constexpr void operator delete (void* ptr, void*) noexcept; constexpr void operator delete[](void* ptr, void*) noexcept;
Modify 17.6.3.4 [new.delete.placement] as indicated:
constexpr void* operator new(std::size_t size, void* ptr) noexcept;[…]
constexpr void* operator new[](std::size_t size, void* ptr) noexcept;[…]
constexpr void operator delete(void* ptr, void*) noexcept;-7- Effects: Intentionally performs no action.
-8- Remarks: Default function called when any part of the initialization in a placement new-expression that invokes the library's non-array placement operator new terminates by throwing an exception (7.6.2.8 [expr.new]).
constexpr void operator delete[](void* ptr, void*) noexcept;-9- Effects: Intentionally performs no action.
-10- Remarks: Default function called when any part of the initialization in a placement new-expression that invokes the library's array placement operator new terminates by throwing an exception (7.6.2.8 [expr.new]).
<stdatomic.h> should provide ATOMIC_CHAR8_T_LOCK_FREESection: 32.5.12 [stdatomic.h.syn] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-11-21 Last modified: 2025-12-04
Priority: Not Prioritized
View all other issues in [stdatomic.h.syn].
View all issues with Tentatively Ready status.
Discussion:
Currently, <stdatomic.h> is specified to provide atomic_char8_t but not its
corresponding ATOMIC_CHAR8_T_LOCK_FREE macro, which is self-inconsistent. Also, given
WG14 N2653 added
ATOMIC_CHAR8_T_LOCK_FREE to C's <stdatomic.h> in C23, perhaps C++ should do
the same thing in the spirit of P3348R4.
[2025-12-04; Reflector poll.]
Set status to Tentatively Ready after seven votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5014.
Modify 32.5.12 [stdatomic.h.syn], header <stdatomic.h> synopsis, as indicated:
template<class T> using std-atomic = std::atomic<T>; // exposition only #define _Atomic(T) std-atomic<T> #define ATOMIC_BOOL_LOCK_FREE see below #define ATOMIC_CHAR_LOCK_FREE see below #define ATOMIC_CHAR8_T_LOCK_FREE see below #define ATOMIC_CHAR16_T_LOCK_FREE see below #define ATOMIC_CHAR32_T_LOCK_FREE see below #define ATOMIC_WCHAR_T_LOCK_FREE see below #define ATOMIC_SHORT_LOCK_FREE see below #define ATOMIC_INT_LOCK_FREE see below #define ATOMIC_LONG_LOCK_FREE see below #define ATOMIC_LLONG_LOCK_FREE see below #define ATOMIC_POINTER_LOCK_FREE see below […]