views:

140

answers:

5

I'm wondering whether this bit of code is exhibiting the correct C++ behaviour?

class Foo
{
public:
    Foo(std::string name) : m_name(name) {}

    Foo(const Foo& other) { 
        std::cout << "in copy constructor:" << other.GetName() << std::endl;
        m_name = other.GetName();
    }

    std::string GetName() const { return m_name; }
    void SetName(std::string name) { m_name = name; }

private:
    std::string m_name;
};

Foo CreateFoo(std::string name)
{
    Foo result(name);
    return result;
}

void ChangeName(Foo& foo)
{
    foo.SetName("foofoo");
}

int _tmain(int argc, _TCHAR* argv[])
{
    Foo fooA("alan");
    std::cout << "fooA name: " << fooA.GetName() << std::endl;
    bool b = true;
    ChangeName(b ? fooA : CreateFoo("fooB"));
    std::cout << "fooA name: " << fooA.GetName() << std::endl;
    return 0;
}

When built in VS2008 the output is:

fooA name: alan
fooA name: foofoo

But when the same code is built in VS2010 it becomes:

fooA name: alan
in copy constructor: alan
fooA name: alan

A copy constructor is being invoked on 'alan' and, despite being passed by reference (or not as the case may be), fooA is unchanged by the called to ChangeName.

Has the C++ standard changed, has Microsoft fixed incorrect behaviour or have they introduced a bug?

Incidentally, why is the copy constructor being called?

A: 

The compiler is clearly evaluating both sides of the :.

Alexander Rafferty
No, it isn't. However, to do type resolution of the result of the expression the *type of* both sides must be evaluated.
Noah Roberts
The compiler is definitely NOT evaluating both sides at run-time.
John Dibling
+5  A: 

A fuller answer:

5.16/4&5:

"4 If the second and third operands are lvalues and have the same type the result is of that type and is an lvalue.

5 Otherwise the result is an rvalue...."

In other words, "bool ? lvalue:rvalue" results in a temporary.

That would be the end of it, however you pass this into a function that, according to C++, MUST receive an lvalue as parameter. Since you pass it an rvalue you actually have code that is not C++. MSVC++ accepts it because it's stupid and uses a bunch of extensions it doesn't tell you about unless you turn it into a pendant. Since what you have is not standard C++ to begin with, and MS is just allowing it by extension, nothing can really be said about what is "correct" regarding it anymore.

Noah Roberts
FuleSnabel
+3  A: 

In your conditional expression, your second operand is an lvalue of type Foo, while the third is an rvalue of type Foo (return value of a function not returning a reference).

This means that the result of the conditional is an rvalue not an lvalue (whatever the value of the first expression), which you can't then bind to a non-const reference. As you've violated this rule you can't invoke the language standard to state what the correct behaviour of either compiler version should be.

The result of a conditional is an lvalue if both second and third operands are lvalues of the same type.

Edit: Technically, both versions are in violation of the standard as neither issued a diagnostic when you violated a diagnosable rule of the standard.

Charles Bailey
Thanks Charles. Noah beat you to the punch, but that answer is spot on.
WalderFrey
A: 

As far as I can tell, this is covered by the C++ standard in 5.16 point 3, isn't it?

It says "if E2 is an rvalue, or if the conversion above cannot be done: if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). [Note: that is, no copy is made.]"

Doesn't this describe the situation above? I thought that it did, but am willing to accept that I may be wrong if someone could explain why it doesn't.

Thanks.

RedHotColes
That whole paragraph only applies if the types of the second and third operands are different ("Otherwise, if the second and third operand have different types..."). Here the types are the same, they are both non-const, non-volatile `Foo` so the paragraph isn't applicable.
Charles Bailey
Thanks Charles - it seems slightly odd to me that if the types are identical (except for the rvalue / lvalue issue) we should end up with some non-standard C++, but if the types are different then we're okay (and references are implied) - at least if I understand this rightly. Thanks for your clarification anyway.
RedHotColes
There's no problem with having one _lvalue_ and one _rvalue_ in a conditional expression, it just means that expression is an _rvalue_. The error in the original code is the attempt to bind this _rvalue_ to a non-const reference; that's the violation of the standard in this case.
Charles Bailey
A: 

Oddly, with reference to the comment "Yep. Turn warning level to 4 and compile again." (Noah Roberts), apparently there is a warning provided that 'ChangeName' takes a non-const reference. If this is a function that takes a const-reference then there's no warning, but the temporary variable is still created. Perhaps this is just another vaguary of the Microsoft compiler.

RedHotColes