When to Prefer a const lvalue Link to rvalue Link Templates

We are currently reading the code base for the cpr request library: https://github.com/whoshuu/cpr/blob/master/include/cpr/api.h

Note that the interface for this library quite often uses perfect forwarding. Just by studying rvalue links, so this is all relatively new to me.

From my point of view, the advantage with rvalue links, templates, and forwarding is that the function call, which will be wrapped around, will take its arguments by rvalue link, and not by value. This avoids unnecessary copying. It also prevents the creation of multiple overloads due to referenced output.

However, in my opinion, the const lvalue reference essentially does the same. This prevents the need for overloading and passes everything by reference. With the caveat that if a function wrapped around accepts a non-constant link, it will not compile.

However, if everything in the call stack does not need a non-constant reference, then why not just pass everything through a const lvalue reference?

I think my main question here is when should you use one over the other for better performance? Trying to verify this using the code below. The following relatively consistent results were obtained:

Compiler: gcc 6.3 OS: Debian GNU / Linux 9

<<<<
Passing rvalue!
const l value: 2912214
rvalue forwarding: 2082953
Passing lvalue!
const l value: 1219173
rvalue forwarding: 1585913
>>>>

. , rvalue arg const l , , , , const lvalue rvalue.

lvalue arg , rvalue forwarding . ? lvalue? Lvalue ?

#include <iostream>
#include <string>
#include <utility>
#include <time.h>

std::string func1(const std::string& arg) {
    std::string test(arg);
    return test;
}

template <typename T>
std::string func2(T&& arg) {
    std::string test(std::forward<T>(arg));
    return test;
}

void wrap1(const std::string& arg) {
    func1(arg);
}

template <typename T>
void wrap2(T&& arg) {
    func2(std::forward<T>(arg));
}

int main()
{
     auto n = 100000000;

     /// Passing rvalue
     std::cout << "Passing rvalue!" << std::endl;

     // Test const l value
     auto t = clock();
     for (int i = 0; i < n; ++i)
         wrap1("test");
     std::cout << "const l value: " << clock() - t << std::endl;

     // Test rvalue forwarding
     t = clock();
     for (int i = 0; i < n; ++i)
         wrap2("test");
     std::cout << "rvalue forwarding: " <<  clock() - t << std::endl;

     std::cout << "Passing lvalue!" << std::endl;

     /// Passing lvalue
     std::string arg = "test";

     // Test const l value
     t = clock();
     for (int i = 0; i < n; ++i)
         wrap1(arg);
     std::cout << "const l value: " << clock() - t << std::endl;

     // Test rvalue forwarding
     t = clock();
     for (int i = 0; i < n; ++i)
         wrap2(arg);
     std::cout << "rvalue forwarding: " << clock() - t << std::endl;

}
+4
1

, . , . , , , , .

Passing rvalue!
const l value: 1357465
rvalue forwarding: 669589
Passing lvalue!
const l value: 744105
rvalue forwarding: 713189

, .

1) wrap1("test"), const std::string &, char, , std::string (.. n times), * . func1, std::string, ( const, , , ), , , - RVO , . , , temp. , , ( , ). , std::string .

2) wrap2("test") const char[5], rvalue func2, std::string const char[], . T const char[5] &&, , , , , rvalue (- , const std::string). , / ( const char[5] ).

3) wrap1(arg) lvalue const string & , func1.

4) wrap2(arg) , T const std::string &.

5) , , , (, temp). "test" std::string("test"), std::string &&, std::forward<T>(arg), . :

Passing rvalue!
const l value: 1314630
rvalue forwarding: 595084
Passing lvalue!
const l value: 712461
rvalue forwarding: 720338

, .

, . , , 2-4.

, , " ++" 23-30. , , , .


* - ; ytoledano , . , , , , .

+3

Source: https://habr.com/ru/post/1685501/


All Articles