4

I've written an answer here: https://stackoverflow.com/a/44481507/2642059 which uses accumulate.

The functor must be binary with a signature like: Ret op(const auto& a, const auto& b) but:

The signature does not need to have const &

The requirement on the binary functor is that it:

Must not invalidate any iterators, including the end iterators, or modify any elements of the range involved

When the object accumulated into is itself a container, I'm a unclear as to the requirements on the functor. For example is something like this allowed?

const auto range = { 0, 1, 2, 3 };
const auto Ret = accumulate(cbegin(range), cend(range), vector<int>(), [](auto& a, const auto& b){
    a.push_back(b);
    return a;
});

Yes, I recognize this is just a copy, I'm not asking for a better solution I'm asking about the validity of this solution.

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 2
    I'm pretty sure it is talking about the iterators of the container it's being applied to. The functor could capture the container it's iterating over and do something with it. Consider an unordered multi-set, you could use accumulate to try to "double" the number of each element in it. This wouldn't be allowed since adding elements to a hash multi set invalidates iterators potentially. – Nir Friedman Jun 12 '17 at 00:19
  • You don't "modify any elements of the range" and "don't invalidate any iterators [of the range]", so it is technically correct. Example on cppreference also shows a similar snippet with `std::string` (and probably shouldn't). – Ivan Aksamentov - Drop Jun 12 '17 at 01:28
  • The signature might as well have `const&` since you're copying the resulting vector on every functor application anyway. But even if you did modify some unrelated range it's all good - "range involved" is only what you pass via first 2 arguments of `accumulate` – Ap31 Jun 12 '17 at 06:09
  • 1
    You may find this related proposal interesting: https://isocpp.org/files/papers/p0616r0.pdf – Arne Vogel Jun 12 '17 at 09:34
  • 1
    @ArneVogel I found that proposal amazing! Thank you so much. I went to find a random post of yours to upvote and found your post on `call_once` and `once_flag`. This is wonderful I never knew about this. Now I feel like I need to go read through all your answers! – Jonathan Mee Jun 12 '17 at 13:40
  • @Drop why do you say "(and probably shouldn't)" I've seen this all over in code for constructing a string. I think it's a pretty common workaround for creating const containers, isn't it? – Jonathan Mee Jun 12 '17 at 13:42
  • @JonathanMee I don't think this *numeric* algorithm is supposed to work on strings, even if it does work. This is something that Alex Stepanov constantly tells in his lectures: while STL allows you for creating multidimensional vectors of maps of sets or to use accumulate the way it likely results in 3*N memory reallocations, it does not necessarily mean you should do this. Accumulate is a generic version of the sum-reduction operation on additive semigroups, like integers. Using a container as accumulator just hides enormous ugly costs behind the pretty interface. – Ivan Aksamentov - Drop Jun 12 '17 at 17:54
  • 1
    @ArneVogel I think the draw toward accumulate primarily stems from the fact that it can generate `const` containers, while other methods cannot. I guess that's the real motivation behind the proposal Arne Vogel references. – Jonathan Mee Jun 12 '17 at 18:14

1 Answers1

3

I think the working draft is more explicit than cppreference or whatever:

In the range [first, last]binary_­op shall neither modify elements nor invalidate iterators or subranges.

Where accumulate is declared as:

template <class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init);

template <class InputIterator, class T, class BinaryOperation>
T accumulate(InputIterator first, InputIterator last, T init, BinaryOperation binary_op);

Therefore I would say that your example is valid, for you are not affecting the range [first, last].

On the other side, the constraint makes perfectly sense for the given range, for you define it with a couple of iterators.
As an example think of what would happen if they were begin and end iterators of a vector at the end of which you decide to push values within binary_op.
As soon as the vector resizes, accumulate goes on working with a couple of dangling pointers. No good.

skypjack
  • 49,335
  • 19
  • 95
  • 187