0

I have a function whose return type is a simple generic lambda expression. (The lambda returned by this function is eventually passed as an argument to STL algorithms like std::transform() or std::accumulate().)

When the lambda does not have an explicit return type, the compiler emits no warnings:

inline auto AccumulateInto() {
    return [](const auto& src, const auto& dst) {return dst + src; };
}

When the lambda has an explicit return type specified:

inline auto AccumulateInto() {
    return [](const auto& src, const auto& dst) -> decltype(dst) {return dst + src; };
}

both compilers emit these similar warnings:

GCC: returning reference to temporary [-Wreturn-local-addr]

MSVC: returning address of a local variable or temporary

Should this warning be heeded because it indicates a shortcoming in the approach (possible undefined behavior!) and should be refactored? or are they "noise"?

I can't determine why specifying the return type explicitly would cause the returned expression to be a "temporary" when it wouldn't be otherwise. Please explain, thanks!

EDIT (to add more context):

The desire to use auto arguments to the lambda is that src and dst might be different types (e.g. one is std::uint8_t and one is std::uint_16t), but I want the return to ALWAYS be the same type as the dst argument, regardless of the type of src.

cigien
  • 57,834
  • 11
  • 73
  • 112
NKatUT
  • 429
  • 3
  • 10
  • 2
    related/dupe: https://stackoverflow.com/questions/17241614/what-expressions-yield-a-reference-type-when-decltype-is-applied-to-them – NathanOliver Dec 01 '20 at 22:14

1 Answers1

3

The point is that decltype(dst) is auto const & (where you can see auto as a template type`, so the lambda return a reference (I repeat: a reference) to a constant object.

The problem is that is a reference to

dst + src

that is: a reference to a temporary value, the temporary object created from the operation dst + src, that doesn't exist anymore when the lambda conclude the execution.

A possible solution: remove -> decltype(dst) or change it to -> decltype(dst+src), so you return a value, not a reference.

Another way (require more typewriting in this case but can be prefereble in more complex cases) could be remove the reference part from the type returned from decltype().

So

-> std::remove_reference_t<decltype(dst)>

or, as suggested by Jarod42, also

-> std::decay_t<decltype(dst)>

If the type of dst support the unary operator + (returning the same type of dst), another simple solution can be

-> decltype(+dst)

This way, +dst is an expression, not a variable, so decltype(+dst) isn't a reference anymore.

max66
  • 65,235
  • 10
  • 71
  • 111
  • `std::decay_t` might be an alternative ( depending of OP' needs). – Jarod42 Dec 01 '20 at 22:28
  • @Jarod42 - Right: the `decltype()` of the sum is a possible solution, not the solution. Answer improved to stress this point. – max66 Dec 01 '20 at 22:38
  • Thank you max66 and Jarod42. I added some additional context in the original post. – NKatUT Dec 01 '20 at 22:40
  • @Jarod42 - D'Oh! I have to sleep more. Thanks. – max66 Dec 01 '20 at 22:42
  • The compiler warning isn't specific, so I assumed the return of the enclosing AccumulateInto() function was problematic, but from your explanations it seems like it's actually the return type of the enclosed lambda expression which is problematic... is this correct? – NKatUT Dec 01 '20 at 22:44
  • @NKatUT - Yes and no: isn't a problem of lambda but a problem regarding all function/functional and (from C++14) the deduction for `auto` as return type. In your first example, the lambda return `dst + src` so is equivalent adding `-> decltype(dst + src)`. When you write `-> decltype(dst)` you impose a different type (the type from `dst + src` can be different from the type of `dst`) and you add a `const` and a `reference. – max66 Dec 01 '20 at 22:49
  • @NKatUT - Anyway: the `std::remove_reference_t`/`std::decay_t` solution should works with your additional requirements; I've also added another possible solution (`-> decltype(+dst)` but is less generic because require that the type of `dst` support the unary operator `+`; should works also `-> decltype(dst+dst)` if the binary operator `+` is supported. – max66 Dec 01 '20 at 22:53
  • Ahh - so if I pass a "uint16_t" as the dst argument, the result of the decltype(dst) is actually a "const uint16_t&" rather than just "uint16_t", since that is how the lamdba's argument inputs it? Thus, the std::remove_reference<> approach converts the explicit return value of the lambda back to "const uint16_t" without the reference. Correct? – NKatUT Dec 01 '20 at 22:56
  • @NKatUT - Almost correct: since that is how the C++ works deducing the returned type for function with `auto` as return type (a lamba is (contains) implicitly a function with an `auto` return type, if you don't explicit it with trailing return type. And `std::remove_reference<>` approach converts the deduced return **type** removing the reference, if present. – max66 Dec 01 '20 at 23:00
  • Please don't remove the c++ tag from C++ questions. It's preferable to remove any other tag instead if the limit of 5 is already reached. – cigien Jan 05 '21 at 23:00