7

please look at the following example code:

   #include <iostream>

    struct Foo {
        Foo() { std::cout << "Default!\n"; }
        Foo(const Foo& foo) { std::cout << "Copy!\n"; }
        Foo(Foo&& foo) { std::cout << "Move!\n"; }
    };

    struct Bar {
        Foo foo;
        Bar() {}
        Bar(Bar &that) : foo(that.foo) {}
        Bar(Bar &&that) : foo(std::move(that.foo)) {}
    };

    Bar f() {
        Bar bar;
        return bar;
    }

    int main() {
        Bar bar(f());
    }

I'am expecting that the output of this code should be:

Default!
Move!

but what I get is:

Default!
Copy!

I can't see any reason why the copy constructor is called instead of the move constructor. If I put the keyword const in front of Bar &that in the declaration of the copy constructor of struct Bar, I've got the right result. I know that it is better for many cases to take a const lvalue reference rather than just an lvalue reference for copy constructors, but I just want to know the reason why this happened.

Why in this example Bar & has been preferred over Bar && even though the return value of f() should be considered as a prvalue? Why the keyword const solves the problem? Does const really solve the problem? Does it have something to do with RVO (Return Value Optimization)? Or, is this just a compiler bug?

I've tested this example on Visual C++ November 2012 CTP.

I've found a similar issue here:

Copy constructor is being called instead of the move constructor

But I still can't understand why.

Can anyone help me?

cigien
  • 57,834
  • 11
  • 73
  • 112
Junekey Jeon
  • 1,496
  • 1
  • 11
  • 18
  • Cannot reproduce. With GCC I just get `Default!` (sane NRVO at work), with `-fno-elide-constructors` I get the expected `Default! Move! Move!`. Maybe a compiler b^Hshortcoming? – Kerrek SB Apr 05 '15 at 15:47
  • change `Bar(Bar &that)` to `Bar(const Bar &that)`. – Emilio Garavaglia Apr 05 '15 at 15:53
  • @KerrekSB 1. I understand the case of NRVO. But why two rather than only one `Move!` is expected when copy elision is set to be off? 2. So are you saying that this is just a bug of the compiler I've used? Thanks anyway. – Junekey Jeon Apr 07 '15 at 14:54
  • @EmilioGaravaglia I've already mentioned that in the question. What I want to know is the reason why `const` makes such difference. – Junekey Jeon Apr 07 '15 at 14:56
  • There's one move from the local `bar` in the function `f` to the return value of `f`, and one move from the constructor argument of `bar` in main. – Kerrek SB Apr 07 '15 at 14:57
  • 2
    Why are you on such an old version? It has literally nothing in its favor -- it wasn't released and was never licensed for production code, so you can't even use the excuse of toolchain stability. – Ben Voigt Apr 08 '15 at 04:32
  • @BenVoigt Cause I don't have a permission to later version... But I wanted to play with C++11/14 (especially variadic templates)... maybe I should move on to other compilers. By the way, did you mean that I should not use CTP's for developing commercial products because of legal issues? or just because of its instability and potential containment of lots of bugs like this? Thank you very much anyway. – Junekey Jeon Apr 08 '15 at 15:56
  • @JunekeyJeon: Both. You'll have to check the actual license to fully understand the legal issues, but [the announcement for that version](http://blogs.msdn.com/b/vcblog/archive/2012/11/02/visual-c-c-11-and-the-future-of-c.aspx) clearly said "**This is a Customer Technology Preview and does not come with a ‘Go Live’ license**". As far as I'm aware, with any CTP you are missing the usual permission for redistribution of the runtime library, so you could be liable for copyright infringement if you ship binaries built with that Visual C++ CTP. – Ben Voigt Apr 08 '15 at 16:05

3 Answers3

5

It's just the usual non-compliance of Visual C++ allowing binding a non-const lvalue reference to a temporary. It violates the language rules, but it went too long before being caught, so now there's code that depends on the bug that would break if it were properly fixed.

That behavior mistakenly allowing the non-const copy constructor to be used, combined with incomplete rvalue reference support in that Visual C++ version, evidently results in the wrong overload being selected.

If you want to use C++11/C++14 features, you'd best stay on top of the latest Visual C++ version.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
3

Wow, when I compile this with...

  1. Visual Studio in Debug I see "Default! Copy!".
  2. Visual Studio in Release I see "Default!".
  3. If you change Bar(Bar &that) to Bar(const Bar &that) then "Default! Move!"
  4. Shockingly, if you switch the order of Bar(Bar &that) with Bar(Bar &&that) (so that the move ctor is defined first) then you'll actually see "Default! Move!"
Psycho
  • 66
  • 2
  • 10
0

Your question is probably answered here.

A temporary object cannot bind to a non-const reference. The copy constructor must take a reference to a const object to be able to make copies of temporary objects.

The other thing is that temporary objects shall not be modifiable as they are to be destroyed anytime soon. Holding a reference to temporaries leads to potential modification on nonexistent objects out of carelessness.

Community
  • 1
  • 1
Thomas Hsieh
  • 731
  • 1
  • 6
  • 26
  • That quote has nothing to do with the behavior being observed. – T.C. Apr 08 '15 at 04:19
  • 1
    @T.C.: That quote has everything to do with it. Not only should the copy constructor be a worse match, it shouldn't even be part of the candidate set. – Ben Voigt Apr 08 '15 at 04:30
  • 1
    @BenVoigt Sure, but that doesn't explain anything. The question is "why was it called?", not "why wasn't it called?" – T.C. Apr 08 '15 at 04:37