Thread: Post-/prefix overloading

  1. #1
    Registered User
    Join Date
    Aug 2009
    Posts
    140

    Post-/prefix overloading

    Hi

    Please take a look at the following example:

    Code:
    01	class Digit
    02	{
    03	private:
    04	    int m_nDigit;
    05	public:
    06	    Digit(int nDigit=0)
    07	    {
    08	        m_nDigit = nDigit;
    09	    }
    10	 
    11	    Digit& operator++(); // prefix
    12	    Digit& operator--(); // prefix
    13	 
    14	    Digit operator++(int); // postfix
    15	    Digit operator--(int); // postfix
    16	 
    17	    int GetDigit() const { return m_nDigit; }
    18	};
    19	 
    20	Digit& Digit::operator++()
    21	{
    22	    // If our number is already at 9, wrap around to 0
    23	    if (m_nDigit == 9)
    24	        m_nDigit = 0;
    25	    // otherwise just increment to next number
    26	    else
    27	        ++m_nDigit;
    28	 
    29	    return *this;
    30	}
    31	 
    32	Digit& Digit::operator--()
    33	{
    34	    // If our number is already at 0, wrap around to 9
    35	    if (m_nDigit == 0)
    36	        m_nDigit = 9;
    37	    // otherwise just decrement to next number
    38	    else
    39	        --m_nDigit;
    40	 
    41	    return *this;
    42	}
    43	 
    44	Digit Digit::operator++(int)
    45	{
    46	    // Create a temporary variable with our current digit
    47	    Digit cResult(m_nDigit);
    48	 
    49	    // Use prefix operator to increment this digit
    50	    ++(*this);             // apply operator
    51	 
    52	    // return temporary result
    53	    return cResult;       // return saved state
    54	}
    55	 
    56	Digit Digit::operator--(int)
    57	{
    58	    // Create a temporary variable with our current digit
    59	    Digit cResult(m_nDigit);
    60	 
    61	    // Use prefix operator to increment this digit
    62	    --(*this);             // apply operator
    63	 
    64	    // return temporary result
    65	    return cResult;       // return saved state
    66	}
    67	 
    68	int main()
    69	{
    70	    Digit cDigit(5);
    71	    ++cDigit; // calls Digit::operator++();
    72	    cDigit++; // calls Digit::operator++(int);
    73	 
    74	    return 0;
    75	}

    I have three questions.

    1) When overloading the postfix operators ++/--, we add the dummy integer variable. Is there any reason why postfix was chosen to need this dummy variable, and not the prefix?

    2) When using x++ (where x is an instance of a class), then does the compiler automatically add the dummy integer? So it calls x++ as x.operator ++(0), e.g.?

    3) When returning the *this-pointer, they use

    Code:
    Digit& operator++(); // prefix
    Why do they include the &? We have had lengthy talks about this, and we agreed that *this is a pointer (by type), but acts like a reference. So personally I would not include the ampersand. What do you say?

    Best,
    Niles.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Niels_M
    1) When overloading the postfix operators ++/--, we add the dummy integer variable. Is there any reason why postfix was chosen to need this dummy variable, and not the prefix?
    The choice may well have been arbitrary, but it could also have been foresight that a typical implementation of prefix operator++ is not less efficient than a typical implementation of the corresponding postfix operator++, so the prefix form is better, all else remaining equal.

    Quote Originally Posted by Niels_M
    2) When using x++ (where x is an instance of a class), then does the compiler automatically add the dummy integer? So it calls x++ as x.operator ++(0), e.g.?
    Yes.

    Quote Originally Posted by Niels_M
    Why do they include the &?
    It is generally more efficient to return by reference where feasible.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Aug 2009
    Posts
    140
    Quote Originally Posted by laserlight View Post
    TIt is generally more efficient to return by reference where feasible.

    Thanks. Why is that the case?


    Also, I have a question on returning references. Instead of creating a new thread, I thought I could just post it here. Please look at

    Code:
    double &GetSomeData() {
    	double h = 46.50;
    	double &hRef = h;
    	return hRef;
    }
    
    
    double &GetSomeData() {
    	double h = 46.50;
    	return h;
    }
    What is the difference between these two functions? In the second case, I do not believe it is obvious that we are returning a reference. But in the first case, I cannot see why we use the & in front of the double, since hRef is still a double.

    Best,
    Niles.

  4. #4
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    You return by reference where feasible to avoid copying the object.

    One reason to avoid copying that relates to operator overloading is that you would not ordinarily want to copy the object that is being changed by operators. Using references makes it clear that all the changes are happening to the same object.

    Another reason you might want to avoid copying in the first place is because copying is sometimes computationally expensive. Say you have a vector of 1 million phrases for a Wheel of Fortune game, or any similar amount of data. You would not necessarily want to copy that around in separate instances too often, if you were faced with the choice, simply because it takes too long. It might take undue resources away from graphics processing or something else.

    I think "where feasible" needs particular emphasis though, because the examples you have shown are returning a reference to a local variable, which is not feasible because it makes the program undefined. Even if it seems to work, the code may not work as you intend on another compiler or even (sometimes) if you only use different switches.

  5. #5
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    Quote Originally Posted by Niels_M View Post
    Also, I have a question on returning references. Instead of creating a new thread, I thought I could just post it here. Please look at

    Code:
    double &GetSomeData() {
    	double h = 46.50;
    	double &hRef = h;
    	return hRef;
    }
    
    
    double &GetSomeData() {
    	double h = 46.50;
    	return h;
    }
    What is the difference between these two functions? In the second case, I do not believe it is obvious that we are returning a reference. But in the first case, I cannot see why we use the & in front of the double, since hRef is still a double.
    Neither of those functions are legal. They both return a reference to a local variable. So there is no difference between them.

    Quote Originally Posted by Niels_M View Post
    Why do they include the &? We have had lengthy talks about this, and we agreed that *this is a pointer (by type), but acts like a reference. So personally I would not include the ampersand. What do you say?
    No, this is a pointer, *this is what it points to.

    As said, you return by reference when it is desireable and feasible to avoid copying the object. Postincrement cannot avoid copying, but preincrement can.
    Last edited by iMalc; 11-09-2010 at 11:57 PM.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  6. #6
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    You seem to be somewhat confused by the difference between a & that denotes a reference variable and a & that is the address-of operator. You might want to review this topic in your favorite C++ resource.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  7. #7
    Registered User
    Join Date
    Aug 2009
    Posts
    140
    Quote Originally Posted by CornedBee View Post
    You seem to be somewhat confused by the difference between a & that denotes a reference variable and a & that is the address-of operator. You might want to review this topic in your favorite C++ resource.
    I feel pretty confident in distinguishing between these two things. As an example:

    Code:
    int test_func_one() {
    	int a = 2;
    	return a;
    }
    
    int* test_func_two() {
    	int a = 2;
    	return &a;
    }
    
    int& test_func_three() {
    	int a = 2;
    	return a;
    }
    The thing that I find is not odd in the three functions in what happens in test_func_three. In test_func_one and test_func_two, the return-type (int and int*, respectively) are precisely what we have in return a and return &a, respectively. In test_func_three we have return a, but a is not a reference. So the compiler creates a reference to a, and copies that to the caller. That is what I find to be somewhat different than usual, i.e. that the compiler is doing something behind our backs when returning references.

  8. #8
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Ah, the wonderful mismatch between variable types and expression types in C++. Yes, it's confusing.

    The simple (but not really correct) explanation is that the compiler automatically creates references when they are required by the context and the value in question allows it. If you're not interested in the technical details, you need read no further.

    The complicated, confusing, but correct answer is that there's really two sides to the type system in C++.
    First, there's the types as you write them. 'int' is a written type. So are 'int*' (pointer to int) and 'int&' (reference to int). Written types are relevant in variable declaration, return types and casts.
    Second, there's the types of expressions. The important thing is that an expression *never* has a reference type. That is, even if you have this:
    Code:
    int i = 0;
    int &ri = i;
    foo(ri);
    int x = ri + 3;
    all usages of ri have type 'int', not type 'int&', even though the variable ri has type 'int&'. But the usage of ri is an expression and thus cannot have reference type.
    The same doesn't apply to pointer types, because pointers are objects in the C++ terminology, unlike references, which are, well, references. In other words, the expression &i has type 'int*'.
    However, expressions have an additional property that is unique to expression types: value category. An expression can be an lvalue or an rvalue. (In C++0x, it can be an lvalue, xvalue or prvalue, but let's not get too confusing.)
    Literals, most function results, the results of most built-in operators, the results of most casts, and some more are rvalues. Rvalues are values that do not really have a location in memory. (Also not entirely correct, but we'll ignore this again as too complicated.)
    Variables, the results of functions that have reference return types, the result of casts to reference types, the result of the pointer dereference operator, and the result of the array index operator are lvalues. Lvalues kinda have a memory location. Lvalue-ness is easily discarded if the context is only interested in the actual value. (E.g. in the addition in the code snippet above, the lvalue-ness of the ri expression is discarded, because + doesn't care about it.)
    Now, some contexts require lvalues. The address-of operator & needs an lvalue as its argument. (That's why &5 is illegal.) Initialization of references (whether as part of variable initialization, return value initialization, or function argument initialization) needs an lvalue, with an exception rule. Cast to reference needs an lvalue. A few other contexts, too.
    The exception rule, by the way, is that references to const (e.g. 'const int&') can be initialized with rvalues, by creating a temporary memory location and copying the value there.

    Rvalue references in C++0x make this a little more complicated, but this is the gist of it in C++03.


    So to explain the example you have, in func_one, the return expression is 'lvalue of int'. Because the return type is 'int', the lvalue-ness is discarded, and the function result is 'rvalue of int'.
    In func_two, the return expression is 'rvalue of int*' (the result of & is an rvalue). An rvalue is exactly what the return type wants, so the function result is also 'rvalue of int*'. As a side note, said pointer points to an invalid memory location the moment the function returns.
    In func_three, the return expression is 'lvalue of int'. The return type is 'int&', so you actually need that lvalue-ness. The reference binds to the lvalue. Because the return type is a reference, the function result is 'lvalue of int'. Again, though, the reference is invalidated the moment the function returns, so actually using the return value is illegal.
    Last edited by CornedBee; 11-10-2010 at 07:02 AM.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  9. #9
    Registered User
    Join Date
    Aug 2009
    Posts
    140
    Quote Originally Posted by CornedBee View Post
    Ah, the wonderful mismatch between variable types and expression types in C++. Yes, it's confusing.

    The simple (but not really correct) explanation is that the compiler automatically creates references when they are required by the context and the value in question allows it. If you're not interested in the technical details, you need read no further.

    The complicated, confusing, but correct answer is that there's really two sides to the type system in C++.
    First, there's ... is illegal.
    Ah, perfect. Thank you for taking the time to write this.

    Best,
    Niles.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Post your games here...
    By Hammer in forum Game Programming
    Replies: 132
    Last Post: 02-28-2013, 09:29 AM
  2. Post your Best Text Adventure
    By Joe100 in forum Game Programming
    Replies: 3
    Last Post: 08-15-2003, 05:47 PM
  3. Auto POST
    By vasanth in forum A Brief History of Cprogramming.com
    Replies: 10
    Last Post: 06-07-2003, 10:42 AM