std::forward(3)
π std::forward(3)
ok so this is std::forward, from the C++ Standard Library (yes that C++ Standard Library, the one with the templates π). she's defined in the header <utility>, and honestly? she's the backbone of perfect forwarding and nobody gives her the credit she deserves. let's get into it π«Ά
β¨ Synopsis β¨
Defined in header <utility>
template< class T > (since C++11)
T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
(until C++14)
template< class T >
constexpr T&& forward( std::remove_reference_t<T>& t ) noexcept; (since C++14)
template< class T > (since C++11)
T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
(until C++14)
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept; (since C++14)
ok so there are two flavours, numbered and everything:
1) this one forwards lvalues as either lvalues or rvalues, depending on T. iconic behaviour tbh.
when t is a forwarding reference (that's a function argument declared as an rvalue reference to a cv-unqualified function template parameter β iykyk π§), this overload forwards the argument to another function with the same value category it had when it was passed into the calling function. she's literally just handing the vibe through untouched. periodt.
for example, if it's used in a wrapper like the one below, the template behaves as described after it:
template<class T>
void wrapper(T&& arg)
{
// arg is always lvalue
foo(std::forward<T>(arg)); // Forward as lvalue or as rvalue, depending on T
}
- if a call to
wrapper()passes an rvaluestd::string, thenTis deduced tostd::string(notstd::string&, notconst std::string&, and notstd::string&&β like, none of those π), andstd::forwardensures that an rvalue reference is passed tofoo. - if a call to
wrapper()passes a const lvaluestd::string, thenTis deduced toconst std::string&, andstd::forwardensures that a const lvalue reference is passed tofoo. - if a call to
wrapper()passes a non-const lvaluestd::string, thenTis deduced tostd::string&, andstd::forwardensures that a non-const lvalue reference is passed tofoo.
2) this one forwards rvalues as rvalues, and β this is the important part, bestie β prohibits forwarding of rvalues as lvalues. she said no β
this overload makes it possible to forward a result of an expression (such as a function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument. so like, the value category survives the whole trip π
for example, if a wrapper doesn't just forward its argument but actually calls a member function on it, and then forwards the result:
// transforming wrapper
template<class T>
void wrapper(T&& arg)
{
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}
where the type of arg may be:
struct Arg
{
int i = 1;
int get() && { return i; } // call to this overload is rvalue
int& get() & { return i; } // call to this overload is lvalue
};
β οΈ real talk: attempting to forward an rvalue as an lvalue β like, say, by instantiating the form (2) with an lvalue reference type T β is a compile-time error. not a warning. not a runtime slap on the wrist. she will not compile. respect it. π
π Notes π
see template argument deduction for the special rules behind forwarding references (T&& used as a function parameter), and see forwarding references for other details. those are the receipts, go read them π
πΈ Parameters πΈ
tβ the object to be forwarded. that's her. that's the whole job π«Ά
β Return value β
static_cast<T&&>(t)
that's it. that's the return value. one line. no notes. π₯
π¦ Complexity π¦
Constant. (finally, a complexity section that doesn't make you cry π)
π Example π
this example demonstrates perfect forwarding of the parameter(s) to the argument of the constructor of class T. also, perfect forwarding of parameter packs is demonstrated. multi-arg slay π
// Run this code
#include <iostream>
#include <memory>
#include <utility>
struct A
{
A(int&& n) { std::cout << "rvalue overload, n=" << n << '\n'; }
A(int& n) { std::cout << "lvalue overload, n=" << n << '\n'; }
};
class B
{
public:
template<class T1, class T2, class T3>
B(T1&& t1, T2&& t2, T3&& t3) :
a1_{std::forward<T1>(t1)},
a2_{std::forward<T2>(t2)},
a3_{std::forward<T3>(t3)}
{}
private:
A a1_, a2_, a3_;
};
template<class T, class U>
std::unique_ptr<T> make_unique1(U&& u)
{
return std::unique_ptr<T>(new T(std::forward<U>(u)));
}
template<class T, class... U>
std::unique_ptr<T> make_unique2(U&&... u)
{
return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}
auto make_B(auto&&... args) // since C++20
{
return B(std::forward<decltype(args)>(args)...);
}
int main()
{
auto p1 = make_unique1<A>(2); // rvalue
int i = 1;
auto p2 = make_unique1<A>(i); // lvalue
std::cout << "B\n";
auto t = make_unique2<B>(2, i, 3);
std::cout << "make_B\n";
[[maybe_unused]] B b = make_B(4, i, 5);
}
π Output π
rvalue overload, n=2
lvalue overload, n=1
B
rvalue overload, n=2
lvalue overload, n=1
rvalue overload, n=3
make_B
rvalue overload, n=4
lvalue overload, n=1
rvalue overload, n=5
look at that output. every value category preserved. chef's kiss π§βπ³π
π See also π
moveβ obtains an rvalue reference (function template, since C++11) π₯move_if_noexceptβ obtains an rvalue reference if the move constructor does not throw (function template, since C++11) π
ok that's the whole page, mwah π this was std::forward(3) from cppreference, page dated 2024.06.10 β so if the standard library moved on without us, that's on the committee, not on me. go forth and forward your value categories correctly bestie, and remember: static_cast<T&&>(t) and nothing else π«Άβ¨π
π¦ where this page comes from
- nixpkgs unstable: stdman 2024.07.05
- opensuse-leap 16.0: stdman 2024.07.05-bp160.1.9 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms