std::forward(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸ’… 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 rvalue std::string, then T is deduced to std::string (not std::string&, not const std::string&, and not std::string&& β€” like, none of those πŸ’€), and std::forward ensures that an rvalue reference is passed to foo.
  • if a call to wrapper() passes a const lvalue std::string, then T is deduced to const std::string&, and std::forward ensures that a const lvalue reference is passed to foo.
  • if a call to wrapper() passes a non-const lvalue std::string, then T is deduced to std::string&, and std::forward ensures that a non-const lvalue reference is passed to foo.

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