7/5/2026 at 1:52:42 PM
Every time I see "use ranges and algorithms!" examples, I am baffled that apparently, I am supposed to find inline double algorithm_call(std::span<double const> xs) noexcept {
return std::accumulate(
xs.begin(),
xs.end(),
0.0,
[](double acc, double volts) {
auto mv = calibrated_mv(volts);
auto err = residual(mv);
return weighted_square(err) + acc;
});
}
more readable, concise, and easier on my eyes than inline double raw_loop(std::span<double const> xs) noexcept {
double sum = 0.0;
for (double volts : xs) {
auto mv = calibrated_mv(volts);
auto err = residual(mv);
sum += weighted_square(err);
}
return sum;
}
Sure, there are some algorithms in <algorithms> that I'm rather not reimplement myself, but this one is not it.
by Joker_vD
7/5/2026 at 4:19:20 PM
You said "ranges and algorithms", but you didn't copy the third function which actually uses <range> library.inline double ranges_pipeline(std::span<double const> xs) noexcept { auto costs = xs | std::views::transform(calibrated_mv) | std::views::transform(residual) | std::views::transform(weighted_square);
return std::ranges::fold_left(costs, 0.0, std::plus<double>{});
}It's still a bit verbose, because C++ doesn't allow universal function call syntax. It will be even more concise in other languages like D.
by Erlangen
7/5/2026 at 5:28:00 PM
That version was so much more opaque that I didn't bother copying that. Again, I'm not entirely sure why people are so enamored with splitting iteration itself from the contents of one iteration step, especially since the loops are language built-ins.by Joker_vD
7/5/2026 at 2:07:18 PM
The first form is easier to send to 32 beefy cores or 1024 small CPUs or a Beowulf cluster or a GPU or people sitting in a room.by rzzzt
7/5/2026 at 2:29:52 PM
Both of them have to be completely rewritten to make use of multiprocessing, so what exactly is the advantage?by xyzzyz
7/5/2026 at 2:47:52 PM
The original example isn't really using ranges except to emulate C++98 iterator work though.The actual equivalent might be something closer to:
inline double algorithm_call(std::span<double const> xs) noexcept {
return std::accumulate(
xs, 0.0,
[](double acc, double volts) {
auto mv = calibrated_mv(volts);
auto err = residual(mv);
return weighted_square(err) + acc;
});
}
(that is, without the boilerplate .begin and .end).Even that is enough to make ranges useful in my mind, but in a codebase which has started to integrate some functional programming techniques, there are also applications for things like views and transforms.
This can make it easier to reason about iteration pipelines in ways you might already be familiar with from POSIX.
That all said, it's C++ so sometimes the error messages get a lot more 'interesting' than they would have with STL-style iterators, especially when mixed with constexpr expressions as you might do with std::format or fmt libs.
by mpyne
7/5/2026 at 2:38:54 PM
The first one too? Isn't that the map-reduce fork-join golden example of multiprocessing?by rzzzt
7/5/2026 at 2:47:10 PM
`std::accumulate` is defined to have sequential semantics, so the analysis required to make it parallel is probably not that different than starting from the loop version. I guess you could have an alternate `accumulate_associative` that uses the same interface but assumes the reduction is associative and has unspecified evaluation order?by cwzwarich
7/5/2026 at 2:51:45 PM
C++ has std::reduce for that, which is std::accumulate except it's defined to operate without any specific ordering.by mpyne
7/5/2026 at 5:35:33 PM
And now you should probably also stop and consider whether adding elements one-by-one as opposed to recursively adding together sums of smaller subarrays has better or worse numerical behaviour in regards to e.g. rounding and stability.by Joker_vD
7/5/2026 at 3:11:39 PM
Thanks everyone, my C++ knowledge has been greatly expanded today.by rzzzt
7/5/2026 at 2:50:57 PM
std::accumulate is sequential and guarantes in order traversal. std::reduce is parallel version of itby CITIZENDOT
7/5/2026 at 2:49:58 PM
1) afaik accumulate cannot be parallelized2) the map part is included in the accumulate lambda, so the map part cannot be parallelized either -> you'd have to split it out into a transform step (iirc)
by tcfhgj
7/5/2026 at 5:32:31 PM
It's been 15 years since I've last touched OpenMP, but the second form is trivially parallelizable as well. Besides, this parallelization can only ever properly work with arrays/vectors or, at the very worst, std::deque as its usually implemented (a vector of fixed-length arrays), not with e.g. linked lists or red-black trees, so why even bother with generic spans and algorithms?by Joker_vD
7/5/2026 at 6:29:54 PM
For compilation?by never_inline
7/5/2026 at 3:57:42 PM
Great, now use some functions. From the library or your own, and see this complexity become manageable.That's what abstraction is about.
by fooker