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, навіть на lvalueWidget&(псує поле при звичайному читанні).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: можна перенаправляти не лише цілі аргументи, а й внутрішні складові об’єкта.