Search code examples
c++11rvalue-reference

Can we use rvalue references for a parameter to be 'passed through'?


Is it possible to 'pass' a parameter through a function without copying?

Let's make up an example:

std::string check(std::string in, int &maxLen)
{
    maxLen = std::max(maxLen, in.length());
    return in;
}

//main:
    int maxCnt = 0;
    std::cout
        << check("test, ", maxCnt)
        << check("two tests, ", maxCnt)
        << check("three tests, ", maxCnt)
        << maxCnt;
// would output: "test, two tests, three tests, 13"

My c++ compiler foo isn't good enough to say if this may already be optimized away.

What would the signature of check(...) have to look like so that a temporary argument would never be copied?

My first guess was:

std::string && check(std::string &&in, int &maxLen)

If this is correct, what would the implementation look like?

Remarks:

  • std::string is a placeholder, it should work with any complex type
  • please hint me to any duplicate question

Solution

  • If you want to avoid any copy of the input string, you should write your function as:

    std::string const& check(std::string const& input, int& maxLen) {
        maxLen = std::max(maxLen, input.size());
        return in;
    }
    

    The argument passed by const reference is pretty obvious. Why the return though? Because RVO cannot elide the copy here. RVO happens when you are building an object (an lvalue) in your function and return it (that includes lvalue arguments as well). The compiler elides the copy by automatically building the object where it is supposed to go. Here, if you return an lvalue (either std::string const or std::string), the compiler will see you want to store the return value somewhere and will have to perform a copy from your reference to that destination. By using a std::string const&, you will avoid that because the operator << from std::basic_ostream handles const references as well.

    To sum up, with the check() definition above:

    int max(0);
    std::string toto("Toto");
    std::string lvalue = check(toto, max); // copy
    std::string const& const_ref = check(toto, max); // no-copy
    std::cout << check(toto, max) << std::endl; // no-copy
    

    The overload resolution in the last call will chose:

    template <class CharT, class Traits, class Allocator>
    std::basic_ostream<CharT, Traits>& 
         operator<<(std::basic_ostream<CharT, Traits>& os,
                    const std::basic_string<CharT, Traits, Allocator>& str);
    //              ^^^^^ const ref here for the string argument