Skip to content

P3732R1: SG9 feedback 2025/11/06 #40

Description

@mhoemmen

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:

  1. Identity value should be optional
  2. ranges::multiplies should be intrinsically imbued with 1 as the identity (for arithmetic or other number types known to the Standard)
  3. 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).

SF F N A SA
4 3 0 0 0

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.

SF F N A SA
0 0 2 4 2

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.

  1. movable-box issue
  2. 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
  3. 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++.

  1. 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 .

  1. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions