| Document #: | P3914R0 |
| Date: | 2025-11-04 |
| Project: | Programming Language C++ |
| Audience: |
LWG |
| Reply-to: |
Tim Song <t.canens.cpp@gmail.com> |
This paper provides wording to resolve the following national body comments on the C++26 CD:
This wording is relative to [N5014] except where noted.
[ Drafting note: During offline discussion, Ruslan Arutyunyan pointed out other issues with the specification of unique and unique_copy and suggested an elegant wording strategy to address them. The wording below incorporates his suggestions. ]
Edit 26.7.9 [alg.unique] as indicated:
template<class ForwardIterator>
constexpr ForwardIterator unique(ForwardIterator first, ForwardIterator last);
template<class ExecutionPolicy, class ForwardIterator>
ForwardIterator unique(ExecutionPolicy&& exec,
ForwardIterator first, ForwardIterator last);
template<class ForwardIterator, class BinaryPredicate>
constexpr ForwardIterator unique(ForwardIterator first, ForwardIterator last,
BinaryPredicate pred);
template<class ExecutionPolicy, class ForwardIterator, class BinaryPredicate>
ForwardIterator unique(ExecutionPolicy&& exec,
ForwardIterator first, ForwardIterator last,
BinaryPredicate pred);
template<permutable I, sentinel_for<I> S, class Proj = identity,
indirect_equivalence_relation<projected<I, Proj>> C = ranges::equal_to>
constexpr subrange<I> ranges::unique(I first, S last, C comp = {}, Proj proj = {});
template<forward_range R, class Proj = identity,
indirect_equivalence_relation<projected<iterator_t<R>, Proj>> C = ranges::equal_to>
requires permutable<iterator_t<R>>
constexpr borrowed_subrange_t<R>
ranges::unique(R&& r, C comp = {}, Proj proj = {});
template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
class Proj = identity,
indirect_equivalence_relation<projected<I, Proj>> C = ranges::equal_to>
requires permutable<I>
subrange<I> ranges::unique(Ep&& exec, I first, S last, C comp = {}, Proj proj = {});
template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
indirect_equivalence_relation<projected<iterator_t<R>, Proj>> C = ranges::equal_to>
requires permutable<iterator_t<R>>
borrowed_subrange_t<R> ranges::unique(Ep&& exec, R&& r, C comp = {}, Proj proj = {});1 Let
predbeequal_to{}for the overloads with no parameterpred, and let E(i) be
- (1.?)
falseifiis equal tofirst; otherwise- (1.1)
bool(pred(*(i - 1), *i))for the overloads in namespacestd;- (1.2)
bool(invoke(comp, invoke(proj, *(i - 1)), invoke(proj, *i)))for the overloads in namespaceranges.2 Preconditions: For the overloads in namespace
std,predis an equivalence relation and the type of*firstmeets the Cpp17MoveAssignable requirements (Table 33 [tab:cpp17.moveassignable]).3 Effects: For a nonempty range, eEliminates all but the first element from every consecutive group of equivalent elements referred to by the iterator
iin the range[first + 1, last)for which E(i) istrue.4 Returns: Let
jbe the end of the resulting range. Returns:5 Complexity: For nonempty ranges, exactly
(last - first) - 1applications of the corresponding predicate and no more than twice as many applications of any projection.
template<class InputIterator, class OutputIterator>
constexpr OutputIterator
unique_copy(InputIterator first, InputIterator last,
OutputIterator result);
template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2>
ForwardIterator2
unique_copy(ExecutionPolicy&& exec,
ForwardIterator1 first, ForwardIterator1 last,
ForwardIterator2 result);
template<class InputIterator, class OutputIterator,
class BinaryPredicate>
constexpr OutputIterator
unique_copy(InputIterator first, InputIterator last,
OutputIterator result, BinaryPredicate pred);
template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2,
class BinaryPredicate>
ForwardIterator2
unique_copy(ExecutionPolicy&& exec,
ForwardIterator1 first, ForwardIterator1 last,
ForwardIterator2 result, BinaryPredicate pred);
template<input_iterator I, sentinel_for<I> S, weakly_incrementable O, class Proj = identity,
indirect_equivalence_relation<projected<I, Proj>> C = ranges::equal_to>
requires indirectly_copyable<I, O> &&
(forward_iterator<I> ||
(input_iterator<O> && same_as<iter_value_t<I>, iter_value_t<O>>) ||
indirectly_copyable_storable<I, O>)
constexpr ranges::unique_copy_result<I, O>
ranges::unique_copy(I first, S last, O result, C comp = {}, Proj proj = {});
template<input_range R, weakly_incrementable O, class Proj = identity,
indirect_equivalence_relation<projected<iterator_t<R>, Proj>> C = ranges::equal_to>
requires indirectly_copyable<iterator_t<R>, O> &&
(forward_iterator<iterator_t<R>> ||
(input_iterator<O> && same_as<range_value_t<R>, iter_value_t<O>>) ||
indirectly_copyable_storable<iterator_t<R>, O>)
constexpr ranges::unique_copy_result<borrowed_iterator_t<R>, O>
ranges::unique_copy(R&& r, O result, C comp = {}, Proj proj = {});
template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
random_access_iterator O, sized_sentinel_for<O> OutS, class Proj = identity,
indirect_equivalence_relation<projected<I, Proj>> C = ranges::equal_to>
requires indirectly_copyable<I, O>
ranges::unique_copy_result<I, O>
ranges::unique_copy(Ep&& exec, I first, S last, O result, OutS result_last,
C comp = {}, Proj proj = {});
template<execution-policy Ep, sized-random-access-range R, sized-random-access-range OutR,
class Proj = identity,
indirect_equivalence_relation<projected<iterator_t<R>, Proj>> C = ranges::equal_to>
requires indirectly_copyable<iterator_t<R>, iterator_t<OutR>>
ranges::unique_copy_result<borrowed_iterator_t<R>, borrowed_iterator_t<OutR>>
ranges::unique_copy(Ep&& exec, R&& r, OutR&& result_r, C comp = {}, Proj proj = {});6 Let
predbeequal_to{}for the overloads in namespacestdwith no parameterpred, and let E(i) be
- (6.?)
falseifiis equal tofirst; otherwise- (6.1)
bool(pred(*i, *(i - 1)))for the overloads in namespacestd;- (6.2)
bool(invoke(comp, invoke(proj, *i), invoke(proj, *(i - 1))))for the overloads in namespaceranges.7 Let:
- (7.1) M be the number of iterators
iin the range[first + 1, last)for which E(i) isfalse;- (7.2)
result_lastberesult+ M +1for the overloads with no parameterresult_lastorresult_r;- (7.3) N be min(M + 1,
result_last-result)8 Mandates:
*firstis writable (24.3.1 [iterator.requirements.general]) toresult.9 Preconditions:
- (9.1) The ranges
[first, last)and[result, result + N)do not overlap.- (9.2) For the overloads in namespace
std:
- (9.2.1) The comparison function is an equivalence relation.
- (9.2.2) For the overloads with no
ExecutionPolicy, letTbe the value type ofInputIterator. IfInputIteratormodelsforward_iterator(24.3.4.11 [iterator.concept.forward]), then there are no additional requirements forT. Otherwise, ifOutputIteratormeets the Cpp17ForwardIterator requirements and its value type is the same asT, thenTmeets the Cpp17CopyAssignable (Table 34 [tab:cpp17.copyassignable]) requirements. Otherwise,Tmeets both the Cpp17CopyConstructible (Table 32 [tab:cpp17.copyconstructible]) and Cpp17CopyAssignable requirements.[ Note 1: For the parallel algorithm overloads in namespace
std, there can be a performance cost if the value type ofForwardIterator1does not meet both the Cpp17CopyConstructible and Cpp17CopyAssignable requirements. For the parallel algorithm overloads in namespaceranges, there can be a performance cost ifiter_value_t<I>does not modelcopyable. — end note ]10 Effects: Copies only the first N element from N consecutive groups of equivalent elements referred to by the iterator
iin the range[first + 1, last)for which E(i) holds isfalseinto the range[result, result + N).11 Returns:
- (11.1)
result + Nfor the overloads in namespacestd.- (11.2)
{last, result + N}for the overloads in namespaceranges, if N is equal to M + 1.- (11.3) Otherwise,
{j, result_last}for the overloads in namespaceranges, wherejis the iterator in[first + 1, last)for which E(j) isfalseand there are exactly N - 1 iteratorsiin[first + 1, j)for which E(i) isfalse.12 Complexity: At most
last - first - 1applications of the corresponding predicate and no more than twice as many applications of any projection.
? Various function templates in subclause 33.9 [exec.snd] can throw an exception of type
unspecified-exception. Each such exception object is of an unspecified type such that a handler of typeexceptionmatches (14.4 [except.handle]) the exception object but a handler of typedependent_sender_errordoes not. [ Note 1: There is no requirement that two such exception objects have the same type. — end note ]
struct not-a-sender { using sender_concept = sender_t; template<class Sndr> static consteval auto get_completion_signatures() -> completion_signatures<> { throw unspecified-exception(); } };where
unspecified-exceptionis a type derived fromexception.constexpr void decay-copyable-result-datums(auto cs) { cs.for-each([]<class Tag, class... Ts>(Tag(*)(Ts...)) { if constexpr (!(is_constructible_v<decay_t<Ts>, Ts> &&...)) throw unspecified-exception(); }); }where
unspecified-exceptionis a type derived fromexception.
4 Let
Qbedecay_t<data-type<Sndr>>.5 Throws: An exception of type
unspecified-exception(33.9.1 [exec.snd.general]) an unspecified type derived fromexceptionif the expressionQ()(env)is ill-formed or has typevoid, whereenvis an lvalue subexpression whose type isEnv.
5 Effects: Equivalent to:
where
unspecified-exceptionis a type derived fromexception.
7 Effects: Equivalent to:
using LetFn = remove_cvref_t<data-type<Sndr>>; auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>(); auto fn = []<class... Ts>(decayed-typeof<set-cpo>(*)(Ts...)) { if constexpr (!is-valid-let-sender) // see below throw unspecified-exception(); }; cs.for-each(overload-set(fn, [](auto){}));where
unspecified-exceptionis a type derived fromexception, and whereis-valid-let-senderistrueif and only if all of the following aretrue:
- (7.1)
(constructible_from<decay_t<Ts>, Ts> && ...)- (7.2)
invocable<LetFn, decay_t<Ts>&...>- (7.3)
sender<invoke_result_t<LetFn, decay_t<Ts>&...>>- (7.4)
sizeof...(Env) == 0 || sender_in<invoke_result_t<LetFn, decay_t<Ts>&...>, env-t...>where
env-tis the packdecltype(let-cpo.transform_env(declval<Sndr>(), declval<Env>())).
7 Effects: Equivalent to:
where
unspecified-exceptionis a type derived fromexception.
7 Let
Isbe the pack of integral template arguments of theinteger_sequencespecialization denoted byindices-for<Sndr>.8 Effects: Equivalent to:
where
unspecified-exceptionis a type derived fromexception.9 Throws: Any exception thrown as a result of evaluating the Effects, or an exception of type
unspecified-exception(33.9.1 [exec.snd.general]) an unspecified type derived fromexceptionwhenCDis ill-formed.
3 The exposition-only class template
impls-for(33.9.2 [exec.snd.expos]) is specialized forstopped_as_optional_tas follows:namespace std::execution { template<> struct impls-for<stopped_as_optional_t> : default-impls { template<class Sndr, class... Env> static consteval void check-types() { default-impls::check-types<Sndr, Env...>(); if constexpr (!requires { requires (!same_as<void, single-sender-value-type<child-type<Sndr>, FWD-ENV-T(Env)...>>); }) throw unspecified-exception(); } }; }where
unspecified-exceptionis a type derived fromexception.
[ Drafting note: The font of ssource-t is corrected by PR 8404. ]
Edit 33.9.12.18 [exec.spawn.future] as indicated:
6 Let ssource-t be an unspecified type that models stoppable-source and default_initializable, such that a default-initialized object of type ssource-t has an associated stop state. Let let ssource be an lvalue of type ssource-t. Let stoken-t be decltype(ssource.get_token()). Let future-spawned-sender be the alias template:
template<sender Sender, class Env>
using future-spawned-sender = // exposition only
decltype(write_env(stop-when(declval<Sender>(), declval<stoken-t>()), declval<Env>()));[ Drafting note: LWG wishes to allow an implementation that returns an object of static storage duration under appropriate conditions. ]
Edit 33.16.3 [exec.sysctxrepl.query] as indicated:
1
query_parallel_scheduler_backend()returns the implementation object for a parallel scheduler.2 Returns: A non-null shared pointerAn object
p, such thatp.get()points to aparallel_scheduler_backendobject that is a base-class subobject of some most derived objectowithin its lifetime. The lifetime ofodoes not end as long as there exists ashared_ptrobjectqwithin its lifetime such thatq.owner_equal(p)istrue. to an object that implements theparallel_scheduler_backendinterface.3 Remarks: This function is replaceable (9.6.5 [dcl.fct.def.replace]).
[ Editor's note: This wording is relative to the current draft, which already includes a change intended to address US 266-399. ]
Edit 33.16.4 [exec.sysctxrepl.psb] as indicated:
1 Preconditions: The ends of the lifetimes of
*this, of the lifetime of the object referred to byr, and of the duration of any storage referenced bysall happen after the beginning of the evaluation of the call toset_value,set_error, orset_doneset_stoppedonr(see below).2 Effects: […]
3 Remarks: […]
virtual void schedule_bulk_chunked(size_t n, bulk_item_receiver_proxy& r,
span<byte> s) noexcept = 0;4 Preconditions: The ends of the lifetimes of
*this, of the lifetime of the object referred to byr, and of the duration of any storage referenced bysall happen after the beginning of the evaluation of the call toset_value,set_error, orset_stoppedonr(see below) one of the expressions below.5 Effects: […]
6 Remarks: […]
virtual void schedule_bulk_unchunked(size_t n, bulk_item_receiver_proxy& r,
span<byte> s) noexcept = 0;7 Preconditions: The ends of the lifetimes of
*this, of the lifetime of the object referred to byr, and of the duration of any storage referenced bysall happen after the beginning of the evaluation of the call toset_value,set_error, orset_stoppedonr(see below) one of the expressions below.8 Effects: […]
9 Remarks: […]
Edit 21.4.13 [meta.reflection.extract] as indicated:
8 Let
Ube the type of the value or object thatrrepresents.9 Returns:
static_cast<T>([:R:]), where R is a constant expression of typeinfosuch that R== ristrue.10 Throws:
meta::exceptionunless
- (10.1)
Uis a pointer type,TandUare either similar (7.3.6 [conv.qual]) or both function pointer types, andis_convertible_v<U, T>istrue,- (10.2)
Uis not a pointer type and the cv-unqualified types ofTandUare the same,- (10.3)
Uis an array type,Tis a pointer type,remove_extent_t<U>*andTare similar types, and the valuerrepresents is convertible toT, or- (10.4)
Uis a closure type,Tis a function pointer type, and the value thatrrepresents is convertible toT.
[ Drafting note: There are a few cases to consider here.
make_signed(^^float))type does not exist (e.g., invoke_result(^^int, {}))value does not exist, or has the wrong type, or is inaccessible, etc. (tuple_size(^^int))For cases 1-3, we require an exception to be thrown.
For case 4, this wording requires a hard error (non-constant); this leaves open the possibility of evolving in the future to either relax the precondition (because functions can be called many times without ODR concerns) or enforce it via throw.
For case 5, some preconditions of type traits can be overly strict. Once case 4 is applied, the remaining cases can in theory produce an answer. The wording makes it unspecified whether the call is non-constant or produces the expected result.
Case 6 remains a hard error; we do not explicitly call this out since it’s just like any template instantiation triggered by a call to a reflection function. ]
? For a function or function template F defined in this subclause, let C be its associated class template. For the evaluation of a call to F, let S be the specialization of C in terms of which the call is specified.
- (?.1) If
then an exception of type
- (?.1.1) the template arguments of S violate a condition specified in a Mandates element in the specification of C;
- (?.1.2) the call is specified to produce a reflection of a type, but S would have no member named
type; or- (?.1.3) the call is specified to return
S::value, but that expression is not a valid converted constant expression of type R, where R is the return type of F;meta::exceptionis thrown. [ Note 1: For the first case, S is not instantiated. — end note ]- (?.2) Otherwise, if the instantiation of S would result in undefined behavior due to dependence on an incomplete type (21.3.2 [meta.rqmts]), then the call is not a constant subexpression.
- (?.3) Otherwise, if the template arguments of S do not meet the preconditions of C, then it is unspecified whether the call is a constant subexpression. If it is, the call produces the result that would be produced if C had no preconditions.
Table 64 [tab:meta.reflection.traits] — Reflection type traits Signature and Return Type Returns
std::UNARY_v<T>::value, where T is the type or type alias represented bytype
std::BINARY_v<T1, T2>::value, where T1 and T2 are the types or type aliases represented byt1andt2, respectively
std::VARIADIC_v<T, U...>::valuewhere T is the type or type alias represented bytypeand U... is the pack of types or type aliases whose elements are represented by the corresponding elements ofargs
std::VARIADIC_v<T1, T2, U...>::valuewhere T1 and T2 are the types or type aliases represented byt1andt2, respectively, and U... is the pack of types or type aliases whose elements are represented by the corresponding elements ofargsA reflection representing the type denoted by
std::UNARY_t<T>::type, whereTis the type or type alias represented bytypeA reflection representing the type denoted by
std::VARIADIC_t<T...>::typewhere T... is the pack of types or type aliases whose elements are represented by the corresponding elements ofargsA reflection representing the type denoted by
std::VARIADIC_t<T, U...>::typewhere T is the type or type alias represented bytypeand U... is the pack of types or type aliases whose elements are represented by the corresponding elements ofargs6 Returns:
rank_v<T>std::rank<T>::value, where T is the type represented bydealias(type).7 Returns:
extent_v<T, I>std::extent<T, I>::value, where T is the type represented bydealias(type)and I is a constant equal toi.8 Returns:
tuple_size_v<T>std::tuple_size<T>::value, where T is the type represented bydealias(type).9 Returns: A reflection representing the type denoted by
tuple_element_t<I, T>std::tuple_element<I, T>:type, where T is the type represented bydealias(type)and I is a constant equal toindex.10 Returns:
variant_size_v<T>std::variant_size<T>::value, where T is the type represented bydealias(type).11 Returns: A reflection representing the type denoted by
variant_alternative_t<I, T>std::variant_alternative<I, T>:type, where T is the type represented bydealias(type)and I is a constant equal toindex.12 Returns:
type_order_v<T1, T2>std::type_order<T1, T2>::value, where T1, T2 are the types represented bydealias(t1)anddealias(t2), respectively.
[N5014] Thomas Köppe. 2025-08-05. Working Draft, Standard for Programming Language C++.
https://wg21.link/n5014