Search code examples
c++referencereturnternary-operator

Ternary operator gives unexpected results in return statement


I'm getting very weird results from ternary operator that should return reference to member object of a linked list. I have member function in my database object like this:

Month& GetLastMonth() { return months_.Size() > 0 ? months_.Last() : Month(); }

I call this function 2 times in code: right after loading DB to get info, and when changing some parameters of month and pushing a button.

First time I write it to const reference, no big deal here. But second time I need non-const reference to recalculate month (so it will change in DB too).

But instead of expected results, some black magic happens. Every time I call GetLastMonth() function it returns a reference with different address. So, after DB loads - its 1 address, recalculating month - its 2nd address and right after saving to file its 3rd address. Of course, because of this, my changes after recalculating month are not saved to file.

Now, size of months_ are always 1 (for testing purposes). Also it's linked list, so Month can never reallocate. I've tested it, and it never calls Month(), so seems like ternary operator is working fine.

Calling it like this, gives same result:

Month& GetLastMonth() { return months_.Size() ? months_.Last() : Month(); }

Howerver if I call it without ternary:

Month& GetLastMonth() { return months_.Last(); }

Or with proper if():

Month& GetLastMonth()
{ 
    if(months_.Size() > 0)
    {
        return months_.Last();
    }
    return Month();
}

It works as expected and returns reference with same address all 3 times. I was thinking about this obscure thing for about 2 days, but still can't find any reasoning behind this.

Edit: Related question: Return type of '?:' (ternary conditional operator)


Solution

  • The ternary operator finds a common type and value category of both operands, and this doesn't happen for the two other cases, which is why it works there.

    months_.Last() and Month() both have type Month, so everything is fine there. But now, let's examine the value categories. months_.Last() is an lvalue, while Month() is a prvalue! So, the common value category here is a prvalue, and both operands get converted to a prvalue. Which means that you get a new Month from months_.Last() every time (from the copy)!

    Note again however, that this is a MS specific extension. Without that extension, the code would be invalid, as you would try to bind the prvalue returned by the conditional operator to a non-const lvalue reference.