AngelosFr
AngelosFr

Reputation: 127

Operator precedence and order of evaluation in the implementation of std::transform

SUMMARY

I stumbled upon the implementation of std::transform and I could not figure out why it works in the case where we transform a container in-place. In particular, I found this expression confusing *d_first++ = unary_op(*first1++);.

CODE

Using the first implementation of std::transform found here https://en.cppreference.com/w/cpp/algorithm/transform, I wrote this sample code:

#include <iostream>

template< class InputIt,
          class OutputIt,
          class UnaryOperation >
OutputIt transform( InputIt first1,
                    InputIt last1,
                    OutputIt d_first, 
                    UnaryOperation unary_op )
{
  while (first1 != last1) {
    *d_first++ = unary_op(*first1++);
  }
  return d_first;
}

int main() {
  int arr[5] {1,2,3,4,5};  

  transform(std::begin(arr),std::end(arr),
        std::begin(arr),
        [](auto i){return i*i;});
  
  for (auto& elem : arr) std::cout << elem << std::endl;  

  return 0;
}

ATTEMPT FOR AN EXPLANATION

After doing some reading in the C++ standard, Cpp reference, and SO, I think I might be able to explain what happens in that confusing (for me) statement. Let's decompose this expression:

*d_first++ = unary_op(*first1++); // same as A = B; 
  1. First resolve what is happening on B, which is, unary_op(*first1++)
  2. Now resolve the parentheses *first1++ which means *(first1++) which means a) make a copy of the dereferenced first1 and b) pass it to the function, then c) increment the iterator.
  3. The copied dereferenced first1 is multiplied by itself inside the unary_op.
  4. Now B is evaluated, time to deal with the assignment.
  5. Assign B to the dereferenced value of d_first.
  6. When this assignment is complete, and before we go to the next statement (whatever that may be) increment the d_first iterator.

This way, the two iterators are "on the same page", meaning at the same position of the arrays which they iterate.

QUESTIONS

My questions are:

A) Is the above explanation correct?

B) Which operations in that expression are evaluations and which computations?

C) Since *first1++ translates to *(first1++) but the dereferencing happens before the incrementation, isn't that confusing to remember? However, (*first1)++ would mean something totally different, i.e. dereference first1 and pass it to the function and then increment this value by one; not the iterator! Any hints to remember how to untangle myself in such expressions?

Upvotes: 1

Views: 139

Answers (1)

j6t
j6t

Reputation: 13387

Just answering C).

Any hints to remember how to untangle myself in such expressions?

*i++, *dst++ = do_something_with(*src++);, etc., are well-established patterns in C++ and C. Therefore, the suggestion is:

  • If you mean *(first++), write it as *first++, because it is the least surprising. (And get used to reading this expression the right way.)

  • If you mean (*first)++, then write it as such. (You have no choice anyway.) The presence of the parentheses indicates immediately, that a meaning is intended that is different from *first++.

Upvotes: 2

Related Questions