1

I have this private member function below, (part of class template, Heap) :

template <typename Task, typename Priority>
const size_t& Heap<Task, Priority>::parent_of(const size_t& index) const
{
    // ** warning C4172: returning address of local variable or temporary
    return (this_index-1)/2;
}

And i am calling it from other function, like below:

template <typename Task, typename Priority>
void Heap<Task, Priority>::bubble_up(const size_t&   start_index)
{
    ....
    if (priority_of(start_index) > priority_of((parent_of(start_index)))) 
    { 
        ... do work ...
        //call recursively the bubble_up
        bubble_up(parent_of(start_index));
    }
    ...
}

The problem is, with priority_of function argument index get corrupted or releases in the 2nd call of the recursion:

template <typename Task, typename Priority>
const Priority& Heap<Task, Priority>::priority_of(const size_t& index) const
{
    return vec.at(index).second;
}

Now, VS have warned me that i am returning address of local variable or temporary (rvalue) in function parent_of, which at the end, this behavior make sense, because when control exists/return from parent_of all local variables including function arguments released!

Now, when changing the function parent_of to return by value (not by const ref), things get to work!

I came from C++98, (so all the rvalue reference is not clear to me) the question is: when and how should i use the rvalue reference (&&) to overcome this ? can i reference (including changing its value) this temp object allocated by the compiler and return reference to it (to be used as return value)?

Adam
  • 2,820
  • 1
  • 13
  • 33
  • 3
    The rule is: Never, Never, Never, return a function local object by reference (any kind of reference). That object will be destroyed at the end of the function leaving you with a dangling reference and using that is undefined behavior. – NathanOliver Nov 11 '20 at 13:33
  • Especially for `size_t`, I don't see any necessity to return it by `const size_t&` in any case. – Scheff's Cat Nov 11 '20 at 13:35
  • Returning (and passing) a `const size_t&` is a pessimization, an indirection that has only cost and no benefit. Just use `size_t`. – molbdnilo Nov 11 '20 at 13:35
  • I know that in my case (when working on size_t type) its costless to return by value! but imagine if the return value is a big structure ! why should i return it by value (which mean construct it) ? i know that i shouldnt return a reference or address from of variables and objects on the local scope, but i am just wondering if i can get the rvaue reference benefits here! – Adam Nov 11 '20 at 13:41
  • 2
    [NRVO](https://en.cppreference.com/w/cpp/language/copy_elision) is usually working in your favor - which means that if you construct your big local object and all branches lead to returning that object, no copy will be done. It'll be created directly in the receivers "memory". – Ted Lyngmo Nov 11 '20 at 13:41
  • @NathanOliver thank you. i am familiar with this rule and i mentioned it in my question but can rvalue reference helps me here? read my above comment. thanks – Adam Nov 11 '20 at 13:42
  • No. You can never return an object from a function by reference. To put it another way, You **must** return function local objects by value. – NathanOliver Nov 11 '20 at 13:43
  • @Scheff and imagine if its not size_t, lets assume it is a big structure, that occupies a lot of memory and cost enough to construct it. – Adam Nov 11 '20 at 13:45
  • 3
    @aðam Thanks to guaranteed copy elision and NRVO, that normally isn't an issue. – NathanOliver Nov 11 '20 at 13:47
  • @TedLyngmo - can you explain more please or leave an answer ? thanks – Adam Nov 11 '20 at 13:47
  • 2
    @aðam [What are copy elision and return value optimization?](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization) – Ted Lyngmo Nov 11 '20 at 13:48
  • 2
    @aðam If you have a big structure that is costly to copy, provide a move semantics for it. Move should be cheap. (And, as others pointed to, no copies/moves will be applied anyway due to NRVO.) – Daniel Langr Nov 11 '20 at 13:50
  • @DanielLangr - thanks i will read about the move semantics and how can use it in my code. – Adam Nov 11 '20 at 13:54
  • 1
    That's why, I started my comment with _Especially for `size_t`_... ;-) However, _copy elision and return value optimization_ were already mentioned multiple times. – Scheff's Cat Nov 11 '20 at 14:14

1 Answers1

5

If you want to preserve the lifetime semantics of a return value depending on the value category of the returned expression, you can't return a const&, or even a && since you will face issues with dangling references.

Instead, you can use decltype(auto) for the return type in order to deduce the appropriate value category of the returned expression:

template <typename Task, typename Priority>
decltype(auto) Heap<Task, Priority>::priority_of(const size_t& index) const
{
    decltype(auto) result = vec.at(index).second;
    return decltype(result)(result);
}

Now the return type will deduce the correct value-category, i.e. l-values for l-value references, and r-values for pr-values (temporaries), and x-values (expiring values).

The cast to decltype(result) in the return statement is used to cast the expression to the appropriate type based on the type of the entity named by the id-expression result.

You need to use this technique for all functions in the call stack where you want to preserve the lifetime semantics.

You can think of this technique as perfect-forwarding, but in the opposite direction, i.e. up the call stack, instead of down.

This answer is based on the technique described in this entertaining lightning talk.

cigien
  • 57,834
  • 11
  • 73
  • 112