0

I have the following code:

struct Foo {
    Foo() = default;
    Foo(const Foo&) { std::cout << "Copy" << std::endl; }
    Foo(Foo&&) noexcept { std::cout << "Move" << std::endl; }
};

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

int main() {
    Foo foo;
    std::cout << "Pass l-value:" << std::endl;
    Bar barL(foo);
    std::cout << "Pass r-value:" << std::endl;
    Bar barR({});
}

It prints the following:

Pass l-value:
Copy
Move
Pass r-value:
Move

How compiler understands that it may not copy r-value?

teplandr
  • 176
  • 1
  • 9
  • 1
    It's a temporary. It does not have a life outside of that function call. No need to copy anything. – Tim Roberts Mar 15 '22 at 03:58
  • you need to understand what is r-value https://stackoverflow.com/questions/9406121/what-exactly-is-an-r-value-in-c – long.kl Mar 15 '22 at 03:59
  • I don't quite understand the question. What output did you expect instead? – user17732522 Mar 15 '22 at 04:08
  • @TimRoberts There is no temporary. There is only a constructor parameter of type `Foo` and a member of the same type. – user17732522 Mar 15 '22 at 04:10
  • @user17732522 When the second `Bar` is constructed it is passed `{}` as an argument, which is a temporary (i.e., an `r-value`). – James Adkison Mar 15 '22 at 04:12
  • [Adding logging to the default constructor](https://ideone.com/rbyRj7) shows that in the 2nd case the `Foo` instance is default constructed as the parameter and then moved. – James Adkison Mar 15 '22 at 04:17
  • @JamesAdkison ["temporary"](https://en.cppreference.com/w/cpp/language/implicit_conversion#Temporary_materialization) has a very specific meaning in C++, and `{}` does not initialize a temporary (it is an rvalue where `Foo foo` in `Bar::Bar(Foo foo)` is the result object). (This is C++17 only, before that the copy from a temporary prvalue might simply be elided) – Artyer Mar 15 '22 at 04:18
  • @Artyer Even before C++17 `{}` is not an expression and should just directly initialize the function parameter. – user17732522 Mar 15 '22 at 04:20

1 Answers1

5

The {} in your second call isn't just an rvalue, it's a prvalue: a pure rvalue. Prvalues don't act like objects so much as they do instructions for initializing an object. Prvalues only get materialized under certain circumstances. Those being primarily when they're used to initialize an object or when bound to a reference.

So when you call Bar barR({}), there's no need to copy (or move) the prvalue that you passed in, because it never gets materialized. Instead, the {} is used to directly initialize the foo parameter of Bar's constructor.


Note: Everything I wrote in this answer is valid for C++17 and later. Prior standards didn't have the prvalue materialization/mandatory copy elision rules yet.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52