What exactly are temporaries in C++ and in which specific cases the compiler will create them?
Asked Answered
F

3

5

I am a C++ newbie and I am struggling on the temporaries topic. I don't find anywhere a clear list of all cases in which the compiler will create a temporary. Actually, a few days ago I had in mind that when we pass an rvalue as const reference parameter, then the compiler will create a temporary and will put the rvalue into that temporary to hold it, passing the temporary as the parameter. Giving this example to my teacher,

void func(const int& _param){}

int main()
{
  func(10);
  // int __tmp__ = 10;
  // func(__tmp__);
  return 0
}

he said in this case the compiler won't create a temporary, but instead will directly optimize the code replacing 10 in all the occurrences inside the function body, so after that, I am more confused than ever.

Featherbedding answered 2/2, 2022 at 17:56 Comment(8)
Read this first: en.cppreference.com/w/cpp/language/as_ifBanquet
I think it doesn't make a lot of sense to discuss too much what is temporary and what is not, because it is really up to the compiler to decided that in the machine code. I can be completely wrong but the only objective thing you can say from a c++ point of view is whether an expression could bind to r-value references. And indeed, in your example 10 would bind to one. i.e. void func(double&& d); ... func(10);. If you want to say that the expression 10 is a temporary I guess you can. godbolt.org/z/1jh4f44joGewgaw
Unrelated useful reading: What are the rules about using an underscore in a C++ identifier?. The TL;DR version is an identifier like __tmp__ is reserved for use by the compiler and supporting libraries and should not be used in any other code. If you use it in your own code you'll probably get away with it. But when you don't get away with it the results are utterly bizarre and nigh-inscrutable, so it's a situation best (and easily) avoided completely.Fulminant
Temporaries are a language concept, so I personally use temporaries as something to help humans reason about how code will work. Start with "If I was a compiler with all optimisation settings disabled ..." and go from there.Landrum
@Gewgaw Under what conditions a temporary is created is at least since C++17 relatively clearly determined, see en.cppreference.com/w/cpp/language/…. In OP's example there is a prvalue-to-xvalue conversion materializing a temporary int object from the prvalue 10. Of course this doesn't mean that the executable produced by the compiler will reserve stack space for it, but they could e.g. take its address and use it the same way as any other object.Drilling
@Drilling i don’t understand what you say means in practice but it looks like the same works on c++14. godbolt.org/z/1M194GGvE (maybe by accident).Gewgaw
@Gewgaw I am not exactly sure what the link is supposed to demonstrate. In the code calling the function will create a temporary double object from the prvalue 10. The reference is bound to that temporary object, not the int prvalue 10 from which the temporary is initialized. More generally what I want to say is that the word "temporary object" has a well-defined meaning in the language specification.Drilling
@user17732522, I was trying to see if the example I put has a difference in C++14 and C++20. It seems that it doesn't, at least for int.Gewgaw
R
4

If you are referring to the language concept rather than what the compiler ends up doing, a temporary in simple terms is a value that ends up arising from some expression and is not bound to any variable. Its value is usually used for some intermediate computation, and then discarded. Here is a somewhat contrived example.

std::string x = "foo";
std::string y = "bar";

auto length = (x + std::to_string(some_number) + y).size();
// __TEMP0__       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// __TEMP1__   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// __TEMP2__  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The result of std::to_string(some_number) is not assigned to anything as it is being used, so that alone is a temporary. It is used in the [sub]expression x + std::to_string(some_number), which creates another temporary string, which itself is +'d with y, creating yet another temporary, whose size is taken and assigned to length. In this case all temporaries involve will exist0 until the end of the evaluation of the whole expression, and then discarded. It'll be as if the code were instead something like this.

std::string x = "foo";
std::string y = "bar";

std::string __TEMP0__ = std::to_string(some_number);
std::string __TEMP1__ = x + __TEMP0__;
std::string __TEMP2__ = __TEMP1__ + y;

auto length = __TEMP2__.size();

Except that the temporaries remain as rvalues and will be destroyed after the evaluation of __TEMP2__.size().


0 Well, it'll be as if they existed. Who knows what your compiler will do to optimize it out.

Rena answered 2/2, 2022 at 18:34 Comment(3)
Respect for numbering your footnotes from zero. Have an upvote for that. (And I think the answer is pretty good too.) But note that the behaviour of the token __TEMP0__&c. is undefined.Banquet
I did the double underscore thing as a sort of visual cue that it's an internal and not an actual variable, but yeah, good to point that out.Rena
wouldn’t be exact as-if code if you put a std::move in each last use of the TEMP variables? just asking.Gewgaw
B
3

The "as-if" rule applies here. See https://en.cppreference.com/w/cpp/language/as_if.

Your job is to specify your intention by writing some C++ source code. The compiler's job is to turn that into machine code that matches the intention.

There's nothing in the C++ standard to tell you how many temporaries are created in your program. A good compiler will optimise your program to

int main(){}

If the creation of the temporary has an observable side-effect then the compiler's options are a little more restricted. But in NRVO and RVO circumstances it is allowed to ignore that. In later C++ standards it is required to.

Banquet answered 2/2, 2022 at 18:3 Comment(8)
Historically I have found that actual compilers don't measure up very well to what "a good compiler will" do. But it has been years since I evaluated this seriously.Schacker
@KarlKnechtel: g++ with O3 switched on will optimise this program to `int main(){}``. They are so aggressive these days. A tiny bit of UB and you're stuffed.Banquet
I am aware of the as-if rule since I have got it while studying Copy Elision topic. But anyway I don't get how to deal with temporaries in an objective way, because it seems to me that every case it's really up to the compiler. The example I gave was just a dummy one in order to let you introduce my doubtsFeatherbedding
Well, what do you actually mean by "deal with" temporaries? Does this impact on how you write the code?Schacker
Since I didn't get temporaries topic I am not sure I can't give you a conscious response, but anyway it's covered in my university lecture, and I would like to understand itFeatherbedding
Are you referring to temporaries as in the language concept instead of what the compiler ends up doing?Rena
I would say yes, actually I am studying it on books, I have never wrote yet any C++ code. So I am trying to get the concept, in order to be aware of what could happen when I will compile my codeFeatherbedding
The number of temporaries created in the sense of stack memory slots allocated in the final program is not well-defined, but the number of temporaries created in the formal description of the abstract machine is relatively determined, with some leeway to the compiler.Drilling
M
1

instead will directly optimize the code replacing 10 in all the occurrences inside the function body

It's up to the compiler whether or not (and how) to optimize this, or anything else. Except for certain exceptions (e.g. copy elision), optimizations are allowed if and only if they don't affect the observable behavior ("as-if rule" mentioned in the other answer).

when we pass an rvalue as const reference parameter, then the compiler will create a temporary and will put the rvalue into that temporary to hold it, passing the temporary as the parameter

Depends on the kind of rvalue. 10 is a prvalue, which are not even objects starting with C++17. References can't bind to prvalues directly, so it's materialized into an xvalue first, which is a temporary.

If you passed an xvalue, the reference would bind directly to it.

The above applies to C++17, I'm not sure how things worked before.

Matchboard answered 2/2, 2022 at 18:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.