P3732R1: SG9 feedback 2025/11/06
Specifying the identity value
Prefer function overloads to traits. __has_identity_value should be a concept to check if identity_value call is well formed.
Agree that it's important to avoid confusion with initial value. (Named parameters in the language would solve this.) The initial value parameter is optional for *reduce. Avoid extra overloads by binding the identity to the binary operation parameter, instead of making it a separate parameter.
Identity value use cases:
- Identity value should be optional
ranges::multiplies should be intrinsically imbued with 1 as the identity (for arithmetic or other number types known to the Standard)
- Ad-hoc lambda with identity value explicitly specified by user
These use cases together justify combining the binary operator with the identity value into one thing. It's clearer as one argument, rather than with the identity value as a "fake named parameter" (e.g., identity_value{0.0}).
Conclusion: Attach identity value to binary operator. Don't try to guess identity value for unknown types.
std::multiplies should do the right thing. But, algorithms should not assume that the identity exists for std::multiplies<void> plus some user-defined number type.
Is binary_operation a struct so that users can use designated initializers? (There may also be constraints on the members that would make it hard to mix up their order.)
POLL: We want reduce(in, init, [](auto lhs, auto rhs) { ... }) to work without providing an identity for the binary operation. The algorithm then doesn't use an identity.
No objection to unanimous consent.
Attendance: 8
POLL: We want reduce(in, init, std::ranges::plus{}) (or multiplies, etc.) to work using the natural identity of the operation whenever that is possible (at least for built-in types, possible [sic] more in a TBD mechanism) by augmenting the definition of the standard library function objects with the identity somehow).
No objection to unanimous consent.
Attendance: 8
POLL: To associate an ad-hoc lambda with an identity, we prefer the option reduce(in, init, binary_operation([](auto lhs, auto rhs) { ... }, identity)) over the options reduce(in, init, [](auto lhs, auto rhs) { ... }, op_identity(identity)) or reduce(in, init, [](auto lhs, auto rhs) { ... }, identity).
Strong consensus in favor.
Attendance: 8
*reduce_into algorithms
Why provide non-parallel *reduce_into overloads? They don't make sense.
Should the output range just be a single iterator? Counterexample is partial_sort_copy, where the output can be shorter than the input (because it's a partial sort).
POLL: We want the serial version of reduce_into (and variants) for consistency now.
No objection to unanimous dissent.
Attendance: 8
POLL: We want (the parallel) reduce_into to take a range as output instead of an output iterator (like all the other parallel range algorithms).
No objection to unanimous dissent.
Projections
Projections are coupled to the identity value, because they permit std::plus on arithmetic types to be used more often (and thus make providing an explicit identity value less necessary).
One justification for not providing projections is that fold_* algorithms do not, for return type deduction reasons. projected_value_t is a C++26 feature, but fold_* algorithms are C++23 features. However, we don't think that would have solved the fold_* algorithms return type issue.
POLL: We want projections.
0/0/2/4/2
Strong consensus against.
Attendance: 9
Author: A, SA
One argument for not providing projections is that we provide transform_* algorithm variants instead. We provide transform_* algorithm variants because it's either hard or impossible to specialize algorithms for specific view types. We would want to specialize algorithms for specific view types for a few reasons.
movable-box issue
- No public interface for "taking apart" a view, e.g., getting the function out of a
transform_view or zip_transform_view, so you can't get the function out in order to avoid the movable-box issue or otherwise optimize
- If
reduce(zip_transform_view(func, x, y), func2) can't be lowered to binary transform_reduce, then the compiler has a lot more work to optimize it (because of the unpleasant tuple<X&, Y&> proxy reference business)
If we control the Standard Library, then why is this a concern? Why can't we just specialize algorithms? We were cited a number of examples in libc++.
for_each is specialized for join_view
for_each optimizes for "segmented iterator" (iterators that represents a flattened range of sub ranges): https://github.com/llvm/llvm-project/blob/main/libcxx/include/__algorithm/for_each.h#L40 .
Here is the implementation of some traits for join_view's iterator to model the "segmented iterator" concepts: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__ranges/join_view.h#L380 .
flat_map's append_range for zip_view
flat_map's append optimization for "product iterator" (iterators that represents a product of few underlying iterators): https://github.com/llvm/llvm-project/blob/main/libcxx/include/__flat_map/utils.h#L99 .
Here is zip_view's iterator to implement some traits to model the "product iterator": https://github.com/llvm/llvm-project/blob/main/libcxx/include/__ranges/zip_view.h#L485 .
Do we want to add a parallel overload of the ranges::iota algorithm?
Yes.
P3732R1: SG9 feedback 2025/11/06
Specifying the identity value
Prefer function overloads to traits.
__has_identity_valueshould be a concept to check ifidentity_valuecall is well formed.Agree that it's important to avoid confusion with initial value. (Named parameters in the language would solve this.) The initial value parameter is optional for
*reduce. Avoid extra overloads by binding the identity to the binary operation parameter, instead of making it a separate parameter.Identity value use cases:
ranges::multipliesshould be intrinsically imbued with 1 as the identity (for arithmetic or other number types known to the Standard)These use cases together justify combining the binary operator with the identity value into one thing. It's clearer as one argument, rather than with the identity value as a "fake named parameter" (e.g.,
identity_value{0.0}).Conclusion: Attach identity value to binary operator. Don't try to guess identity value for unknown types.
std::multipliesshould do the right thing. But, algorithms should not assume that the identity exists forstd::multiplies<void>plus some user-defined number type.Is
binary_operationa struct so that users can use designated initializers? (There may also be constraints on the members that would make it hard to mix up their order.)POLL: We want
reduce(in, init, [](auto lhs, auto rhs) { ... })to work without providing an identity for the binary operation. The algorithm then doesn't use an identity.No objection to unanimous consent.
Attendance: 8
POLL: We want
reduce(in, init, std::ranges::plus{})(or multiplies, etc.) to work using the natural identity of the operation whenever that is possible (at least for built-in types, possible [sic] more in a TBD mechanism) by augmenting the definition of the standard library function objects with the identity somehow).No objection to unanimous consent.
Attendance: 8
POLL: To associate an ad-hoc lambda with an identity, we prefer the option
reduce(in, init, binary_operation([](auto lhs, auto rhs) { ... }, identity))over the optionsreduce(in, init, [](auto lhs, auto rhs) { ... }, op_identity(identity))orreduce(in, init, [](auto lhs, auto rhs) { ... }, identity).Strong consensus in favor.
Attendance: 8
*reduce_intoalgorithmsWhy provide non-parallel
*reduce_intooverloads? They don't make sense.Should the output range just be a single iterator? Counterexample is
partial_sort_copy, where the output can be shorter than the input (because it's a partial sort).POLL: We want the serial version of
reduce_into(and variants) for consistency now.No objection to unanimous dissent.
Attendance: 8
POLL: We want (the parallel)
reduce_intoto take a range as output instead of an output iterator (like all the other parallel range algorithms).No objection to unanimous dissent.
Projections
Projections are coupled to the identity value, because they permit
std::pluson arithmetic types to be used more often (and thus make providing an explicit identity value less necessary).One justification for not providing projections is that
fold_*algorithms do not, for return type deduction reasons.projected_value_tis a C++26 feature, butfold_*algorithms are C++23 features. However, we don't think that would have solved thefold_*algorithms return type issue.POLL: We want projections.
0/0/2/4/2
Strong consensus against.
Attendance: 9
Author: A, SA
One argument for not providing projections is that we provide
transform_*algorithm variants instead. We providetransform_*algorithm variants because it's either hard or impossible to specialize algorithms for specific view types. We would want to specialize algorithms for specific view types for a few reasons.movable-boxissuetransform_vieworzip_transform_view, so you can't get the function out in order to avoid themovable-boxissue or otherwise optimizereduce(zip_transform_view(func, x, y), func2)can't be lowered to binarytransform_reduce, then the compiler has a lot more work to optimize it (because of the unpleasanttuple<X&, Y&>proxy reference business)If we control the Standard Library, then why is this a concern? Why can't we just specialize algorithms? We were cited a number of examples in libc++.
for_eachis specialized forjoin_viewfor_eachoptimizes for "segmented iterator" (iterators that represents a flattened range of sub ranges): https://github.com/llvm/llvm-project/blob/main/libcxx/include/__algorithm/for_each.h#L40 .Here is the implementation of some traits for
join_view's iterator to model the "segmented iterator" concepts: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__ranges/join_view.h#L380 .flat_map'sappend_rangeforzip_viewflat_map'sappendoptimization for "product iterator" (iterators that represents a product of few underlying iterators): https://github.com/llvm/llvm-project/blob/main/libcxx/include/__flat_map/utils.h#L99 .Here is
zip_view's iterator to implement some traits to model the "product iterator": https://github.com/llvm/llvm-project/blob/main/libcxx/include/__ranges/zip_view.h#L485 .Do we want to add a parallel overload of the
ranges::iotaalgorithm?Yes.