3

I am trying to know whether a prvalue is same as a temporary in C++17. Consider the following example,

//C++17 example
#include <iostream>
struct Custom 
{
    Custom()
    {
        std::cout<<"default constructor called"<<std::endl;
    }
    Custom(const Custom& var)
    {
        std::cout<<"copy constructor called"<<std::endl;
    }
    ~Custom()
    {
         std::cout<<"destructor called"<<std::endl;
    }
};
Custom func()
{
    Custom temp;
    return temp;//this will use copy constructor 
}
int main() {
    func();
}

Assuming the above code is executed with the -fno-elide-constructors options enabled, my question is whether the prvalue that is created as a copy of the local object named temp inside the function func is the same as a temporary in C++17.

I have read that in C++17, a prvalue is not an object. For example, here the user says that

"Prvalues are not objects (since C++17)".

Similarly, here the user says

"Before C++17 the prvalue was already the temporary, which is what your quote refers to. Since C++17 the prvalue itself is not a temporary..."

But that confused me because if a prvalue is not an object, then what is it? I mean, in the above example a copy of temp is created using the copy constructor. Now, prior to C++17, that copy is a temporary object which is an prvalue. But what is changed in C++17. My thinking is that we still have an prvalue that is created using the copy constructor and that prvalue is a temporary in C++17 because it exists for some limited duration.

PS: I may be wrong in describing what is actually happening, so please correct me by explaining what is happening in C++17 and how is it different from C++14. I am also asking because i have read on SO posts(similar posts) that in the above example, there is no temporary involved in C++17 which i do not understand how it is possible.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • You jumped from "prvalue" to "rvalue". The value category taxonomy has *two* things under "rvalue", the other being a "xvalue" (which always denote objects). *Pure* rvalues not being objects works by virtue of the type system towing the line between the two. – StoryTeller - Unslander Monica Mar 21 '22 at 10:55
  • @StoryTeller-UnslanderMonica That was a typo(now corrected). It was meant to be `prvalue`. – Jason Mar 21 '22 at 11:01
  • Do you want to compare `return temp;` with `return Custom{};`? The later won't call copy constructor (in C++17). – Jarod42 Mar 21 '22 at 11:07
  • 1
    _I have read that in C++17, a prvalue is not an object_ prvalue was not an object even before C++17, it has always been a kind of expression. – Language Lawyer Mar 21 '22 at 11:09
  • @Jarod42 No, i don't want to replace it with `return Custom{};` because i am not looking for "how to avoid copying". I am looking for explanation about whether when we do `return temp;` a temporary object is created using the copy constructor. – Jason Mar 21 '22 at 11:21
  • @LanguageLawyer [Here](https://stackoverflow.com/a/71531502/17881448) the user says that *"Prvalues are not objects (since C++17)"*. – Jason Mar 21 '22 at 11:22
  • Thats just sloppy use of terminology. _If the "object" doesn't exist, it isn't an object (i.e. if its lifetime hasn't started or has already ended, AND its constructor nor destructor are currently running)_ Is also not correct, e.g. a pointer to an object outside lifetime is a thing. – Language Lawyer Mar 21 '22 at 11:24
  • 1
    _I am looking for explanation about whether when we do `return temp;` a temporary object is created using the copy constructor_ A temporary object is created because [`func()` in `func();`](https://timsong-cpp.github.io/cppwp/n4868/stmt.expr#1.sentence-2) is a [discarded-value expression](https://timsong-cpp.github.io/cppwp/n4868/expr.context#def:discarded-value_expression), not because it is `return ...` in a function with non-reference return type. – Language Lawyer Mar 21 '22 at 11:25
  • @LanguageLawyer [Here](https://stackoverflow.com/questions/71448625/copy-constructor-is-not-called-when-storing-result-of-overloaded-operator/71448741?noredirect=1#comment126287955_71448741) the user says *"Before C++17 the prvalue was already the temporary, which is what your quote refers to. Since C++17 the prvalue itself is not a temporary..."* – Jason Mar 21 '22 at 11:30
  • @Anya LanguageLawyer is correct that I was sloppy in my quote. "prvalue" is a property of an expression. However a prvalue is associated with the creation of a temporary in many contexts in C++14, see https://timsong-cpp.github.io/cppwp/n4140/class.temporary#1. – user17732522 Mar 21 '22 at 11:51
  • @user17732522 So what happens in my example above(which is very similar to to the [linked question](https://stackoverflow.com/q/71448625/17881448)) in C++17 and C++14. I mean will the prvalue that is created using the copy constructor result in a temporary or not(for both C++14 and C++17). – Jason Mar 21 '22 at 12:13
  • 1
    @Anya I don't have time right now to write an answer, but LanguageLawyer already explained what happens in C++17: The temporary is created because the function call is a discarded-value expression. In C++14 the temporary is also created, but simply because it is the returned prvalue of the function (see my link above). – user17732522 Mar 21 '22 at 12:17
  • @user17732522 Ok, i will try to understand what is going on from your given link and the above mentioned comments. If you get time, please consider adding an answer. Thanks for the comments. – Jason Mar 21 '22 at 12:19

1 Answers1

4

But that confused me because if an prvalue is not an object, then what is it?

An expression that can become an object.

My thinking is that we still have an prvalue that is created using the copy constructor and that prvalue is a temporary in C++17 because it exists for some limited duration.

The prvalue isn't an object yet (or thought of differently, here).

Let's extend your example by using func to initialise something.

int main() {
    auto obj = func();
}

obj in main and temp in func are "the same object". It is like if func was instead

void func(Custom * result) {
    Custom & temp = *new(result) Custom;
}

int main() {
    char storage[sizeof(Custom)];
    Custom & obj = *reinterpret_cast<Custom *>(storage);
    func(&obj);
    obj.~Custom();
}

Or with -fno-elide-constructors leaving the copy of temp into the return value:

void func(Custom * result) {
    Custom temp;
    new(result) Custom(temp);
}

What changed between C++14 to C++17 is instead of that being an permitted divergence from the behaviour of the abstract machine, the definition of the abstract machine was changed to have that behaviour.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • 1
    `obj` and `temp` are the same object only if NRVO is also applied, which OP prevents with the compiler flag. – user17732522 Mar 21 '22 at 12:13
  • @user17732522 added a note about that – Caleth Mar 21 '22 at 12:31
  • @Anya it isn't an example of exactly correct C++, but a notion of how RVO is implemented. `new(result) Custom` constructs a `Custom` in the storage pointed to by `result`. Search "placement new" – Caleth Mar 21 '22 at 12:54
  • @Caleth Ok so both of the examples in your code are *pseudo-code*? If yes, it would be good to mention that in your code as well and some explanations perhaps would make it much better. – Jason Mar 21 '22 at 13:23
  • @Anya No, I just wrote it without checking, there should have been a dereference – Caleth Mar 21 '22 at 13:29