std::forward_like (заголовок <utility>, C++23) робить одну річ: застосовує категорію значення (lvalue/rvalue) і константність типу Owner до значення типу Target.

std::forward<T>(x) перенаправляє сам x згідно з T. Після появи deducing this часто потрібно перенаправити не self, а його поле, «ніби зверталися через self».

Проблема

struct Widget {
    std::string name;

    template <typename Self>
    decltype(auto) get_name(this Self&& self) {
        // потрібно повернути name з тією ж категорією, що й self
    }
};
  • return std::move(self.name); — завжди move, навіть на lvalue Widget& (псує поле при звичайному читанні).
  • return std::forward<Self>(self).name; — працює, але шумно.
  • return std::forward<Self>(self.name); — помилка: Self — тип віджета, аргумент — std::string.

Рішення:

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

Матриця поведінки

std::forward_like<Owner>(val) дивиться на Owner і перетворює val:

Якщо Owner є… Результат для std::string
Widget& std::string&
const Widget& const std::string&
Widget або Widget&& std::string&&
const Widget&& const std::string&&

Концептуальна реалізація

Спрощено (ідея стандарту):

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);
}

Трюк std::is_rvalue_reference_v<T&&>:

  • T = Widget& → T&& згортається в Widget& → результат &;
  • T = Widget або Widget&& → T&& дає Widget&& → результат &&.

Приклад: універсальний аксесор

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 з std::string&&

forward_like доповнює perfect forwarding: можна перенаправляти не лише цілі аргументи, а й внутрішні складові об’єкта.