0

My question is : is it legal to return a reference to an input variable which is passed by reference. I borrow the example from C++: Life span of temporary arguments? and return by rvalue reference

#include <iostream>

#include <string>

class MatrixClass
{
public:
int m_value;

std::string m_str;

MatrixClass(int a)
{
    m_value = a;
    std::cout << "hello: " << m_value << '\n';
}

MatrixClass(const MatrixClass& A)
{
    m_value = A.m_value;

    std::cout << "hello: " << m_value << '\n';

    if (A.m_str == "temp_in_*")
    {
        std::cout << "string: " << "copied from temp_in_*" << '\n';
    }

}

void operator=(const MatrixClass& A)
{
    m_value = A.m_value;

    std::cout << "hello: " << m_value << '\n';

    if (A.m_str == "temp_in_*")
    {
        std::cout << "string: " << "copied from temp_in_*" << '\n';
    }
}

~MatrixClass()
{
    std::cout << "bye: " << m_value << '\n';

    if (m_str == "temp_in_*")
    {
        std::cout << "string: " << "temp_in_*" << '\n';
    }
}

};

MatrixClass& operator+(MatrixClass& tempClassA, MatrixClass& tempClassB)
{
    tempClassA.m_value += tempClassB.m_value;
    return tempClassA;
}

MatrixClass operator*(MatrixClass& tempClassA, MatrixClass& tempClassB)
{
    MatrixClass Temp(101010);
    Temp.m_value = tempClassA.m_value * tempClassB.m_value;

    Temp.m_str = "temp_in_*";

    return Temp;
}

int main()
{
    MatrixClass A1(2);
    MatrixClass A2(3);
    MatrixClass A3(10);
    MatrixClass A4(11);
    MatrixClass A5(12);

    std::cout << "start of expression " << '\n';

    MatrixClass A6(0);

    A6 = A1 * A2 + A3 * A4 + A5 * A6;

    std::cout << "end of expression " << '\n';

    std::cout << "A6.m_value: " << A6.m_value << '\n';

    std::system("pause");
}

the operator return a reference to its input which is a temporary variable, and pass it to another operator: operator*(A1, A2) return a temporary variable, also operator*(A3, A4), operator*(A5, A6)

Is there any problem with the lifetime of the temporary variables ? I am developing a Matrix class.

What happens if the expression is more complicated, such as:

    (A+B*C)*((A*B + C)*A)

A general question is (take from return by rvalue reference)

is this possible:

change

A compute(A&& v)
{
  (do something to v) 

  return v;
}

to

A& compute(A& v)
{
  (do something to v) 
  return v;
}
Community
  • 1
  • 1
liangbright
  • 142
  • 1
  • 9
  • 2
    I know it's just an example, but have you tried compiling and running that code? Aside from some syntax errors, it never copies any of the objects, and never creates any temporary variables. – Apples Mar 07 '14 at 15:39
  • That's a rather odd usage of `operator*`, because you actually implement `operator*=`. Just define `operator*` properly (i.e. return an object, not a reference). – stefan Mar 07 '14 at 16:35
  • thanks stefan. operator* is just an example to show my question: is it legal to return a reference to the input variable which is passed by reference – liangbright Mar 07 '14 at 17:06
  • @liangbright: yes, that's how you would actually implement `operator*=` or `operator<<` for example. What you shouldn't do though is return a reference to a temporary object. – stefan Mar 07 '14 at 17:24
  • @stefan thanks. I updated the example and tested in vs2013. It gives the correct answer although temporary objects are passed around by reference. In my real Matrix class, I indeed need to create some temporary objects. I could return them from functions by value (use std::move), and pass them to functions by reference. But If I pass and return them by reference, no objects are copied -> save time. I tested my class on vs2013, it is fine. But I do not know if it is c++11 standard or just only vs2013 supports it. – liangbright Mar 07 '14 at 17:43
  • @liangbright As I've said: Don't return references to temps, it's unsafe. The move will be done automatic by the compiler, you just write `Type foo() { Type object; return object; }`. – stefan Mar 07 '14 at 17:48
  • @stefan your example is very different from mine. operator*(A1, A2) return a temporary variable by value, then it is passed to operator+(?, ?) by reference. This temporary variable lives on stack of main() -- as I check on vs2013, not local stack of a function – liangbright Mar 07 '14 at 18:57
  • Note that binding a temporary to a non-const lvalue reference is not allowed in C++. There is a language extension in MSVC that allows it. But your code will get nonportable, and in C++11 we have rvalue reference for this purpose. That said, try to understand what a full-expression is. Then you'll know how long your temporaries live. – dyp Mar 07 '14 at 20:07
  • @dyp thanks. I guess this is what you suggested: T represents MatrixClass: T operator+(T&& A, T& B) { A.m_value += B.m_value; return std::move(A); } – liangbright Mar 07 '14 at 20:42
  • or this way ? : T&& operator+(T&& A, T& B) { A.m_value += B.m_value; return A; } – liangbright Mar 07 '14 at 21:05

1 Answers1

1

Yes, it is legal for a function to return references to input parameters, in the sense that it will compile and there are many uses where it will work without problems. The lifetime of any temporaries created in an expression is the lifetime of the full expression or statement, so as long as the reference is not used beyond the expression, the usage should work fine. It is somewhat risky, though, because the caller may not be aware of the reference propagation the function does, and the special rules for extension of temporary lifetimes don't generally apply when the reference is passed through an intermediary function.

Your examples involve modifying and returning a reference to non-const lvalue instance. These types of uses, in general, will be harder to run into the pitfalls than references to const or references to rvalues. A reference to non-const lvalue can't be bound to a temporary (at least not without going through some hoops to trick the compiler), so you will generally have to pass an actual l-value (non-temporary) named variable (or other long-lived object) into them. When that reference is then passed out of the function as a return value, it will refer to whatever long-lived object was passed in. (You can still get into trouble if you don't properly manage lifetimes, of course, but at least the lifetimes we're talking about in this case are generally more than a single statement/expression.)

If you pass rvalue references through your function, and especially if they get translated to a non-const lvalue somewhere down the expression tree (which is somewhat easy to do since the language, as a safety feature, decays rvalues into lvalues whenever they're bound to a name), the temporary nature of the reference can be lost and it is easier to accidentally bind the reference to a long-lived reference, which would outlive the temporary that it is bound to (which generally won't live beyond the statement/full-expression in which it is created). This is why I generally favor returning (and usually passing) r-values by value rather than by reference. Then, the compiler is more aware of the lifetime issues and the usages are generally more foolproof. In many cases, the compiler can elide the move constructions anyway, and when it can't, moves are generally cheap.

Adam H. Peterson
  • 4,511
  • 20
  • 28
  • thanks. I guess what you are suggesting is in the main() { T aaa; pass aaa to function: T& FuncA(T&) by reference: FuncA(aaa), and return a reference (T&) from FuncA } is legal. But pass T FuncB() which returns a temp object (unnamed) with type T, to FuncA as FuncA(FuncB()) is not good. The solution is to change T& FuncA(T&) to T&& FuncA(T&&) or T FuncA(T&&). Is that what you are suggesting? – liangbright Mar 07 '14 at 21:23
  • Your example wouldn't work. `FuncB()` returns an rvalue, which can't be bound to a non-`const` lvalue reference. (Try it --- you should get a compile error.) If `FuncA` were instead: `T const &FuncA(T const &t) { return t; }`, then yes, you would have a problem. `FuncA(FuncB())` would return a reference to a temporary whose lifetime ends at the end of the current statement, so you'll hit UB if you try to save and use that reference beyond the statement. The solution depends on what you're trying to do, but `T &&FuncA(T &&)` usually won't be it; that won't do anything to fix the lifetimes. – Adam H. Peterson Mar 07 '14 at 21:34
  • *"A reference to non-const lvalue can't be bound to a temporary (at least not without going through some hoops to trick the compiler)"* These "tricks" include the default settings of the MSVC compiler: a language extension that allows it. Unfortunately. – dyp Mar 07 '14 at 21:39
  • so, T FuncA(T&& A) { modify A such as A=A+1; return std::move(A)} is a solution? then I can use FuncA(FuncB) ? This is borrowed from http://stackoverflow.com/questions/13430831/return-by-rvalue-reference/13431082#13431082 – liangbright Mar 07 '14 at 21:48
  • @dyp thanks. but I really need to use FuncA(FuncB()) without using auto A = FuncB() ; FuncA(A); any solution? – liangbright Mar 07 '14 at 21:49
  • @liangbright On a side note, enclose code in backticks `\`` to format it as inline code. Of course you can return references to parameters, but you'll have to watch out for lifetime issues. The last step in a full-expression containing such a computation always has to store the result in a non-temporary. (Can you guarantee that, or at least make it hard to make an error here, and if one makes a mistake, can that be easily detected?) – dyp Mar 07 '14 at 21:53
  • Yes, that would avoid the safety issues, since the reference isn't being propagated through the call back to the caller; a new value is semantically being created with a new lifetime that the compiler will manage properly for you. However, I would prefer `T FuncA( T a ) { ++a; return a; }`. This way, `FuncA` works correctly on both lvalues and rvalues and doesn't have to do any copies (unless you pass it an lvalue, where a copy would be correct). In theory, all the moves could be optimized away as well, although in practice the calling conventions of most compilers will require one move. – Adam H. Peterson Mar 07 '14 at 21:53
  • @Adam, thanks. `T FuncA( T aaa ) { ++aaa; return aaa; }` in release mode, I guess the compiler may use named return value optimization, or other methods. But in debug mode, many copies of aaa may be created which can take much time (e.g., 512x512 matrix). So I want the debug version to be not very slow. Maybe pass temp object with T&&, return value with std::move(temp object) is better in my application – liangbright Mar 07 '14 at 22:03
  • No copies should be done. If you pass an rvalue to `FuncA` and the compiler does any copies (assuming `T` has a move constructor, of course), that compiler is broken. – Adam H. Peterson Mar 07 '14 at 22:05
  • just tried in vs2023 debug mode: `T FuncA( T&& aaa ) { a= a+1; return aaa; }` used by FuncA(FuncB) , set break point on a = a+1, the move constructor T(T&&) (not the copy constructor T(const T&)) is called, then the break point is hit – liangbright Mar 07 '14 at 22:27
  • just disabled vs2013 Language Extensions, and it works just as Adam said. Excellent ! thanks to dyp and Adam – liangbright Mar 07 '14 at 22:32