Currently reading the codebase for cpr requests library: https://github.com/whoshuu/cpr/blob/master/include/cpr/api.h
Noticed that the interface for this library uses perfect forwarding quite often. Just learning rvalue references so this is all relatively new to me.
From my understanding, the benefit with rvalue references, templating, and forwarding is that the function call being wrapped around will take its arguments by rvalue reference rather than by value. Which avoids unnecessary copying. It also prevents one from having to generate a bunch of overloads due to reference deduction.
However, from my understanding, const lvalue reference essentially does the same thing. It prevents the need for overloads and passes everything by reference. With the caveat that if the function being wrapped around takes a non-const reference, it won't compile.
However if everything within the call stack won't need a non-const reference, then why not just pass everything by const lvalue reference?
I guess my main question here is, when should you use one over the other for best performance? Attempted to test this with the below code. Got the following relatively consistent results:
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 >>>> These results stay fairly consistent between runs. It appears that for an rvalue arg, the const l value signature is slightly slower, though I'm not exactly sure why, unless I'm misunderstanding this and const lvalue reference does in fact make a copy of the rvalue.
For lvalue arg, we see the counter, rvalue forwarding is slower. Why would this be? Shouldn't the reference deduction always produce a reference to an lvalue? If thats the case shouldn't it be more or less equivalent to the const lvalue reference in terms of performance?
#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; }
T&&- with The being a template type parameter in a deduction context (aka function parameters) is something different than an r-value reference (likestd::string&&). The former will bind to anything, the latter only to temporaries.std::forward<T&>(arg)should bestd::forward<T>(arg). Sorry for spamming the comments.