3

I tutor students in C++, and recently came across a problem involving pointer arithmetic with array names. The main thing I'm confused about is the statement

T min_value = *begin++; 

Cplusplus tells me that the ++ operator has higher precedence than the * dereference operator, so I assume that begin is first incremented and then dereferenced. Also, this site confirms that when you pass the name of an array to a function, it gets turned into a pointer to the address of the first element, element [0]. However, when I run the code below in Visual Studio, it looks like min_value is set to be 1.5 at the beginning, which seems to contradict what I think the order of operations are.

I think it should be:

  1. increment the begin pointer to the [1] element (2nd in the array)
  2. dereference the pointer value
  3. set min_value to be equal to the 2nd element in the array.

However, my experiment seems to indicate that instead something different is happening:

  1. dereference pointer value
  2. set min_value equal to 1st element of array
  3. increment pointer to next element

Can someone clarify this?

// Problem #3: Please write the implementation of min() function and max() function..

#include <iostream> 
using namespace std; 
template<typename T> 

T min(T* begin, T* end) 
{ 
        T min_value = *begin++; 
        while(begin != end) // You can use for-loop too. 
        { 
                if( *begin < min_value) 
                        min_value = *begin; 
                begin++; 
        } 
        return min_value; 
} 
template<typename T> 
T max(T* begin, T* end) 
{ 
        T max_value = *begin++; 
        while(begin != end) 
        { 
                if( *begin > max_value) 
                        max_value = *begin; 
                begin++; 
        } 
        return max_value; 
} 
int main() 
{ 
        double arr[] = {    1.5, 4.5, 3.5, 2.5, 5.5 }; 
        int values[] = {    1, 2, 3, 4, -1, 5 }; 
        cout << "min of arr[] is : " << min(arr, arr + 5) << endl; 
        cout << "min of values[] is : " << min(values, values + 6) << endl; 
        cout << "max of arr[] is : " << max(arr, arr + 5) << endl; 
        cout << "max of values[] is : " << max(values, values + 6) << endl; 
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
wyverniv
  • 279
  • 1
  • 2
  • 11
  • By the way, you should teach your students not to use `using namespace` if they are beginners. Otherwise, they will end up using it at global scope in headers. Additionally, `min` and `max` are particularly hot candidates for name collisions, because there are `std::min` and `std::max` functions lurking in ``. – Christian Hackl Jul 25 '15 at 09:20

3 Answers3

3

Precedence is only a rule for how the code should be parsed. ++ comes first, and * comes second. But when the code is executed, you have to consider what the operators actually do.

In your case, the following happens:

  1. A copy of begin is made.
  2. The original is incremented.
  3. The copy is returned.
  4. The copy is dereferenced.
  5. The copy is assigned to min_value.

That's just how the post-increment operator works, and it's also how you write the operator when you overload it for your own types:

T operator++(int)
{
    T copy = *this;
    ++(*this);
    return copy;
}

Actually, in the case of the built-in post-increment operator, incrementation does not necessarily have to be step 2. It could also happen at a later point, as long as the observable behaviour is the same. For example, nothing stops the compiler from incrementing the original value after it has returned the copy. You could not perform such a thing in your own, overloaded operator, of course.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
2

This expression

T min_value = *begin++; 

can be imagined the following way

auto temp = begin;
T min_value = *temp;
++begin;

According to the C++ Standard (5.2.6 Increment and decrement)

1 The value of a postfix ++ expression is the value of its operand. [ Note: the value obtained is a copy of the original value —end note ] ...The value computation of the ++ expression is sequenced before the modification of the operand object.

In general case the function definitions are wrong because the range specified by begin and end can be empty and begin can point beyond a valid range. In this case you may neither increase begin nor dereference it.

So it would be more correctly to write for example the following way

template <typename T>
T * min( T* begin, T* end ) 
{
    T *min_value = begin;
 
    if ( begin != end )
    {
        while( ++begin != end )
        { 
            if( *begin < *min_value ) min_value = begin; 
        } 
    }

    return min_value; 
} 

In this case the call of the function will look like

cout << "min of arr[] is : " << *min(arr, arr + 5) << endl;
                               ^^^
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • This makes sense, can you explain a little more on why this is the case? Specifically why the dereferencing and incrementing seem to happen independently? – wyverniv Jul 25 '15 at 09:07
  • @wyverniv: They don't happen indepedently. It's just that the postfix `++` does not *only* increment, it also copies. So the sequence is copy -> increment -> return copy -> dereference copy. – Christian Hackl Jul 25 '15 at 09:15
  • @wyverniv See my updated post, The value of an expression with the postfix increment operator is the value of the operand before applying the incrementing. – Vlad from Moscow Jul 25 '15 at 09:20
1

You should not confuse the return value of an operator and the priority.

The first is dealing with what the operator returns, the second deals with when something happens.

So if you have:

T min_value = *begin++;

Here is how it works:

  1. operator++ - it increments the pointer, but returns the pointer that was there originally.
  2. operator* - dereferences the pointer returned previously, returns T that it pointed to.
  3. operator= stores left-hand side into right-hand side, returns right-hand side.

You don't use the last return value, but you theoretically could.

Note here, that in #2 you use the return from #1, rather than accessing the pointer again.

v010dya
  • 5,296
  • 7
  • 28
  • 48