-1

I have this simple piece of code in C++17 and I was expecting that the move constructor was called (or the copy constructor, if I was doing something wrong), while it is just calling the normal constructor and I cannot why is doing this optimization.

I am compiling with -O0 option.

#include <iostream>

using namespace std;

struct Foo {
  int m_x;

  Foo(int x) : m_x(x) { cout << "Ctor" << endl; }

  Foo(const Foo &other) : m_x(other.m_x) { cout << "Copy ctor" << endl; }

  Foo(Foo &&other) : m_x(other.m_x) {
    other.m_x = 0;
    cout << "Move ctor" << endl;
  }
};

void drop(Foo &&foo) {}

int main() { drop(Foo(5)); }
Kill KRT
  • 1,101
  • 2
  • 17
  • 37
  • 1
    You're passing an `int` and not an `Foo` object. – Jason May 08 '22 at 15:35
  • 1
    It's not "optimisation". It's C++17 (the standard) mandated behaviour. https://stackoverflow.com/q/38043319/817643 – StoryTeller - Unslander Monica May 08 '22 at 15:38
  • In C++, a *constructor* has a specific semantic, and it **can be** (pre-C++17) elided or **must be** (C++17 and later) elided under some circumstances. Any side-effects (such as in the OP code) are not guaranteed to be executed, and should only be benign (as they are in the OP code, since they are just used for logging), and not relied upon or critical. – Eljay May 08 '22 at 15:42
  • Single parameter constructors should _always_ be marked `explicit` - https://www.sjbrown.co.uk/posts/always-use-explicit/ – Den-Jason May 08 '22 at 15:45

2 Answers2

2

I cannot why is doing this optimization.

This is not due to any optimization in C++17. Instead this is due to the fact that you're passing an int when you wrote Foo(5). And since you've provided a converting constructor Foo::Foo(int), it will be used to create a Foo object which will then be bound to the rvalue reference parameter of drop.

Note that in C++17, even if we were to make the parameter of drop to be of type Foo instead of Foo&&, then also there will be no call to the move constructor because of mandatory copy elison.


C++11

On the other hand, if you were using C++11 and using the flag -fno-elide-constructors and parameter to drop was of type Foo instead of Foo&& then you could see that a call would be made to the move ctor.

//--------vvv-----------> parameter is of type Foo instead of Foo&&
void drop(Foo foo) {
    
    std::cout<<"drop called"<<std::endl;
    
    }

int main() { 
    drop(Foo(5)); //in c++11 with -fno-elide-constructors the move ctor will be called
}

The output of the above modified version in C++11 with -fno-elide-constructors is:

Ctor
Move ctor
drop called

Demo

Jason
  • 36,170
  • 5
  • 26
  • 60
1

In function main you create a temporary Foo object from integer 5 but you don't move (nor copy) from it anywhere. To actually call your move (or copy) constructor, you have to move- (or copy-) construct another object from your temporary Foo.

E.g., to call Foo's move constructor:

void drop(Foo &&foo) {
    // Move-construct tmp from foo.
    Foo tmp { std::move(foo) };
}
paolo
  • 2,345
  • 1
  • 3
  • 17
  • I was thinking that since I am passing a temporary object (so a rvalue) it should call the move constructor (since `drop` is taking a rvalue reference as input parameter). That it is what is confusing me. – Kill KRT May 08 '22 at 15:49
  • @KillKRT The move constructor will be called in C++11 when the parameter of `drop` function is of type `Foo` and not `Foo&` (with `-fno-elide-constructors`) as explained in my answer. – Jason May 08 '22 at 15:50
  • 1
    @KillKRT The r-value parameter of `drop` binds to the temporary `Foo` but doesn't move it. The actual move will be performed when you construct an object from your r-value reference parameter. If you think about it, this is similar to `Foo f{5}; auto const& fRef = f`. Here, you're binding reference `fRef` to `f`: you don't copy nor move anything. – paolo May 08 '22 at 15:56