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 lvalueWidget&(destroys the member on a normal read).return std::forward<Self>(self).name;— works, but noisy.return std::forward<Self>(self.name);— error:Selfis the widget type, the argument isstd::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 toWidget&→ result&;T = WidgetorWidget&&→T&&isWidget&&→ 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.