Quoting from item 3 ("Understand decltype") of "Effective Modern C++" :
However, we need to update the template’s implementation to bring it into accord with Item 25’s admonition to apply std::forward to universal references:
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i];
}
Why are we forwarding 'c' in the last statement of 'authAndAccess'? I understand the need for forwarding when we're passing the param to another function from within the original function (to be able to correctly call the overloaded version which takes an rvalue reference or if there's an overloaded version which takes param by value then we can move instead of copy), but what benefit does std::forward give us while calling indexing-operator in the above example?
The example below also verifies that using std::forward does not allow us to return an rvalue reference from authAndAccess, for instance if we wanted to be able to move (instead of copy) using the return value.
#include <bits/stdc++.h>
void f1(int & param1) {
std::cout << "f1 called for int &\n";
}
void f1(int && param1) {
std::cout << "f1 called for int &&\n";
}
template<typename T>
void f2(T && param) {
f1(param[0]); // calls f1(int & param1)
f1(std::forward<T>(param)[0]); // also calls f1(int & param1) as [] returns an lvalue reference
}
int main()
{
std::vector<int> vec1{1, 5, 9};
f2(std::move(vec1));
return 0;
}
It depends on how the Container is implemented. If it has two reference-qualified operator[]
for lvalue and rvalue like
T& operator[] (std::size_t) &; // used when called on lvalue
T operator[] (std::size_t) &&; // used when called on rvalue
Then
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i]; // call the 1st overload when lvalue passed; return type is T&
// call the 2nd overload when rvalue passed; return type is T
}
It might cause trouble without forwarding reference.
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return c[i]; // always call the 1st overload; return type is T&
}
Then
const T& rt = authAndAccess(Container{1, 5, 9}, 0);
// dangerous; rt is dangling
BTW this doesn't work for std::vector
because it doesn't have reference-qualified operator[]
overloads.