9
#include <utility>
template <typename Container>
decltype(auto) index(Container &&arr, int n) {
    return std::forward<Container>(arr)[n];
}

Make a function call :

#include <vector>
index(std::vector {1, 2, 3, 4, 5}, 2) = 0;

When function calling finished, the object std::vector {1, 2, 3, 4, 5} will be destroyed, assigning a value to a deallocated address would cause undefined behaviour. But the above code works well and valgrind detected nothing. Maybe the compile helps me make another invisible variable like

auto &&invisible_value {index(std::vector {1, 2, 3, 4, 5}, 2)};
invisible_value = 9;

If my guess is incorrect, I want to know why assigning a value to an rvalue reference returned from function is worked and when the temporary object index(std::vector {1, 2, 3, 4, 5}, 2) will be destroyed.

This idea originated from 《Effective Modern C++》, Item3 : Understand decltype.

Evg
  • 25,259
  • 5
  • 41
  • 83
Jonny0201
  • 433
  • 5
  • 10
  • How do you imagine function chaining works if you can't access the returned value from a function without storing it? The fact that you are returning an rvalue reference in this case is not really relevant. An rvalue reference is also an lvalue. – super Jun 03 '20 at 06:45

2 Answers2

7

You said "When function calling finished, the object vector {1, 2, 3, 4, 5} will be destroyed" but that is untrue. The temporary created for the function call is not deleted until the statement ends, i.e. the next line of code. Otherwise imagine how much code would break that passes c_str() of a temporary string.

Patrick Parker
  • 4,863
  • 4
  • 19
  • 51
  • 1
    Not quite sure about this, do you have any sources? The vector is in fact not returned, but instead passed to a function, so it should be destroyed when `index` returns. Instead, what I think is going on is that a copy of element `n` is returned as an rvalue reference, which would survive until the calling scope ends and thus can be assigned another value. However, I could also be wrong here. :-) – Carsten Jun 03 '20 at 05:56
  • 5
    @Carsten _"All temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created"_ [Source](https://en.cppreference.com/w/cpp/language/lifetime#Temporary_object_lifetime) – cdhowie Jun 03 '20 at 06:00
  • @cdhowie, [but](http://eel.is/c++draft/expr.call#7) *"It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression."* – Evg Jun 03 '20 at 06:59
  • @Evg The parameter is the reference, not the temporary. – cdhowie Jun 03 '20 at 07:01
  • @cdhowie If the function signature were `index(Container arr, int n)`, would that change anything? When `arr` would be destroyed? – Evg Jun 03 '20 at 07:10
  • @cdhowie Your quote, however, refers to temporaries, not references. I think this is more fitting: *"a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference."* ([Source](https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary)). From my understanding, the *expression* in this case ends with the return statement (i.e. after `{index(vector {1, 2, 3, 4, 5}, 2)}`). – Carsten Jun 03 '20 at 07:18
  • 1
    @Carsten Of course it refers to temporaries. We are talking about the lifetime of temporaries. The quote I pasted indicates that temporaries are destroyed as the last step in evaluating the full-expression where they appear. That clearly indicates that the `vector` temporary exists until after the assignment to the reference returned by the function. The question is whether the `vector` still exists when the assignment happens, because if it does not the code invokes UB. The answer is: yes, it does exist because until the assignment happens, the full-expression hasn't been evaluated. – cdhowie Jun 03 '20 at 07:24
  • 1
    @Evg Yes. It is now implementation-defined whether `arr` has been destroyed when the assignment happens, which means that the assignment either succeeds or invokes UB. – cdhowie Jun 03 '20 at 07:29
  • 2
    @Carsten Also, the quote that you pasted actually agrees with the quote I pasted, and contradicts your claim that the expression ends with the return statement. The full-expression where the temporary is created is `index(vector {1, 2, 3, 4, 5}, 2) = 0;`, so _this_ is the full-expression that the temporary is guaranteed to outlive. Whatever happens in `index()` is irrelevant to this point. – cdhowie Jun 03 '20 at 07:31
  • @cdhowie, this makes sense, thanks. One more question. Take a look at this (now deleted) [answer](https://stackoverflow.com/a/59812045), please. If I change `std::initializer_list` to `std::initializer_list&&`, would this fix the problem with lifetime of a temporary? – Evg Jun 03 '20 at 07:32
  • @Evg I believe so. The problem with the code as you have it written is that the temporary is used to construct the argument. Changing the argument to be a reference leaves the temporary alone; it would _not_ be moved from. – cdhowie Jun 03 '20 at 07:34
  • @cdhowie so the second statement in the following code is a ub too `std::vector && vec = std::vector{1, 3, 4, 5,67, 7}; std::cout << vec[4];` – asmmo Jun 03 '20 at 08:18
  • @asmmo Actually, that's fine! Binding a temporary to a local reference extends the lifetime of the temporary to match the lifetime of the reference. This only works if you bind the temporary directly to the reference; binding it through a function call does not work. – cdhowie Jun 03 '20 at 08:25
0

invisible_value = 9; is completely legal assignment as 9 is indeed a temporary. rvalue references can be assigned with a temporary but not bound to an lvalue (for example a variable; but you can achieve this like below:

int a =10;
invisible_value=std::move(a);

https://godbolt.org/z/iTNGFr. Mode detailed explanation in this question C++ Double Address Operator? (&&).

edit:

the assignment is only legal if it is in the same scope. invisible_value in this case refers to something that was in the scope of index function and it's behavior is undefined if you have a reference to it.

Community
  • 1
  • 1
djacob
  • 110
  • 1
  • 9
  • 1
    No, this is wrong. In the `invisible_value` case, the vector is destroyed after initialization of `invisible_value` and it is a dangling reference. `invisible_value = anything;` is UB at that point. – cdhowie Jun 03 '20 at 07:36
  • yes! you are right. my mistake. The above point is only valid if it is in same scope. The `invisible_value` case is a reference from the function and it's scope/lifetime ends in the function. – djacob Jun 03 '20 at 09:21