std::forward_like (<utility>, C++23) does one job: apply the value category (lvalue/rvalue) and constness of an Owner type to a Target value.

std::forward<T>(x) forwards x itself according to T. After deducing this, you often need to forward not self, but a member, as if it were accessed through self.

The problem

struct Widget {
    std::string name;

    template <typename Self>
    decltype(auto) get_name(this Self&& self) {
        // need to return name with the same category as self
    }
};
  • return std::move(self.name); — always moves, even on an lvalue Widget& (destroys the member on a normal read).
  • return std::forward<Self>(self).name; — works, but noisy.
  • return std::forward<Self>(self.name); — error: Self is the widget type, the argument is std::string.

Solution:

return std::forward_like<Self>(self.name);

Behaviour matrix

std::forward_like<Owner>(val) inspects Owner and converts val:

If Owner is… Result for std::string
Widget& std::string&
const Widget& const std::string&
Widget or Widget&& std::string&&
const Widget&& const std::string&&

Conceptual implementation

Simplified (standard idea):

template <class T, class U>
[[nodiscard]] constexpr auto forward_like(U&& x) noexcept
    -> /* ResultType */ {
    using UnrefT = std::remove_reference_t<T>;
    using PureU  = std::remove_reference_t<U>;

    using ConstU = std::conditional_t<
        std::is_const_v<UnrefT>, const PureU, PureU>;

    using ResultType = std::conditional_t<
        std::is_rvalue_reference_v<T&&>,
        std::remove_reference_t<ConstU>&&,
        std::remove_reference_t<ConstU>&
    >;

    return static_cast<ResultType>(x);
}

The std::is_rvalue_reference_v<T&&> trick:

  • T = Widget& → T&& collapses to Widget& → result &;
  • T = Widget or Widget&& → T&& is Widget&& → result &&.

Example: a universal accessor

template <typename T>
struct Box {
    T value;

    template <typename Self>
    constexpr auto&& unwrap(this Self&& self) noexcept {
        return std::forward_like<Self>(self.value);
    }
};

Box<std::string> box{"data"};

auto& ref = box.unwrap();                 // std::string&
const auto& cbox = box;
auto& cref = cbox.unwrap();               // const std::string&
std::string extracted =
    Box<std::string>{"temp"}.unwrap();    // move from std::string&&

forward_like completes the perfect-forwarding story: you can forward not only whole function arguments, but also inner parts of an object.