a universal reference is not an rvalue reference

T&& means two different things depending on context, and mixing them up is the single most common source of confusion here. On a concrete, non-deduced type, T&& is an ordinary rvalue reference — it only binds to rvalues. In a context where T is itself being deduced (a function template parameter, or auto&&), T&& is a forwarding reference (Scott Meyers' term "universal reference" is the more widely recognized name for the same thing) — it binds to anything, lvalue or rvalue, and T is deduced differently depending on which.

void f(Widget &&w);              // NOT a universal reference — Widget is concrete, not deduced.
                                    // Only binds to rvalues, exactly like any other rvalue reference.

template <typename T>
void g(T &&arg);                 // universal reference — T is deduced right here, at the call site.

Widget w;
g(w);           // arg binds to an lvalue — T deduced as Widget&, so T&& collapses to Widget&
g(Widget());     // arg binds to an rvalue — T deduced as Widget, so T&& stays Widget&&
            
The deduction context is what matters, not the && syntax by itself — the same two characters mean something entirely different depending on whether a type is being deduced right there or was already fixed beforehand.

reference collapsing

C++ doesn't allow a "reference to a reference" written directly in source, but template and auto deduction can still produce one internally — when that happens, the compiler collapses the two references into one according to a fixed rule: an lvalue reference wins. Only "rvalue reference to rvalue reference" collapses to an rvalue reference; every other combination collapses to an lvalue reference.
Written internallyCollapses to
T& &T&
T& &&T&
T&& &T&
T&& &&T&&
This is exactly the mechanism behind the g(w) example above: T is deduced as Widget&, the parameter's declared type T&& becomes Widget& &&, and that collapses to Widget& — an ordinary lvalue reference, which is why arg can bind to the lvalue w at all.

std::forward vs. std::move

std::move is unconditional: it always casts its argument to an rvalue reference, regardless of what was passed in. std::forward<T> is conditional: it casts to an rvalue reference only if T was deduced from an rvalue in the first place — otherwise it passes the argument through as the lvalue it already was. That's the entire mechanism "perfect forwarding" refers to: preserving whatever value category the caller originally used, through one extra layer of function call.

template <typename T>
void wrapper(T &&arg) {
  // target(arg);                 // WRONG here — arg is itself a named variable, so it's always an lvalue
                                   // inside wrapper's body, even when T&& deduced as an rvalue reference
  target(std::forward<T>(arg));   // RIGHT — forwards arg as whatever it originally was at the call site
}
            
The comment above is the trap almost everyone hits once: a named parameter is always an lvalue expression inside the function body, no matter what type it was declared with. std::forward<T>(arg) is what recovers the original rvalue-ness when T says there was one — plain arg never does, and neither does std::move(arg), which would force an rvalue cast even for a caller who passed an lvalue on purpose.

why this matters: avoiding a copy the wrapper doesn't need

The entire point in practice: a wrapper function (an emplace-style factory, a logging shim, anything that receives an argument only to hand it straight to something else) shouldn't force a copy that the final destination could have avoided by moving instead. Without forwarding, a wrapper taking T by value or by const& either copies unconditionally or can never move; with a universal reference plus std::forward, the wrapper is exactly as cheap as calling the target function directly, whether the caller passed an lvalue or a temporary.

traced: an rvalue and an lvalue through the same wrapper

A Widget that prints which constructor runs, forwarded through a wrapper twice — once with a named variable (lvalue), once with a temporary (rvalue) — so you can see the copy constructor and move constructor fire on exactly the calls that should trigger each one: TopNotchNote/cpp/perfect_forwarding_wrapper_demo.cpp

where to go from here

Move Semantics — std::move, the rule of five, and the ordinary (non-deduced) rvalue references this page builds on.
Templates & Generics — the template type deduction that makes a universal reference possible in the first place.

reference

cppreference — std::forward
cppreference — reference collapsing