this is my implementation of std::forward
template<typename T, typename U>
constexpr decltype(auto) forward(U && u) noexcept
{
return static_cast<T &&>(u);
}
compared to the standards
template<typename T>
constexpr T && forward(typename std::remove_reference<T>::type & __t) noexcept
{
return static_cast<T &&>(__t);
}
template<typename T>
constexpr T && forward(typename std::remove_reference<T>::type && __t) noexcept
{
return static_cast<T &&>(__t);
}
the standard uses the remove_reference to ensure usage of:
forward<T>(value)
which then requires them to overload each rvalue / lvalue instance of forward. Whereas I am using a second template parameter to ensure the correct usage, which allows me not need an overload.
can anyone point out any downfalls in my implementation?
Note: decltype(auto) can be replaced with T && for the same results.
-
5\$\begingroup\$ You should read n2951 by Howard Hinnant. \$\endgroup\$Snowhawk– Snowhawk2017年07月07日 04:34:41 +00:00Commented Jul 7, 2017 at 4:34
-
1\$\begingroup\$ You should ask somebody on the standard's committee. Also I bet they already have a million unit tests around the standard; why don't you find those and see if your version works for all those tests? \$\endgroup\$Loki Astari– Loki Astari2017年07月07日 16:26:36 +00:00Commented Jul 7, 2017 at 16:26
1 Answer 1
Your version is less type-safe than the normal implementation.
std::forward()
constrains the argument's value (after dereferencing) to be of the same type as the return type.
Your implementation can perform any type conversion supported by static_cast
. That allows this erroneous code to compile:
struct Base {};
struct Derived : Base {};
int main()
{
forward<Derived>(Base{});
}
If std::forward()
is used instead, we get an error:
error: no matching function for call to ‘forward<Derived>(Base)’
14 | std::forward<Derived>(Base{});
| ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
We can fix the function to correctly reject the problem code. With C++20, we'd do that by adding a requires
clause to constrain U
:
#include <type_traits>
template<typename T, typename U>
requires (std::is_lvalue_reference_v<U> || !std::is_lvalue_reference_v<T>)
&& std::is_convertible_v<std::remove_reference_t<U>*,
std::remove_reference_t<T>*>
constexpr decltype(auto) forward(U && u) noexcept
{
return static_cast<T &&>(u);
}
I don't think the decltype(auto)
adds clarity, so I would actually write
constexpr T&& forward(U&& u) noexcept
{
return static_cast<T&&>(u);
}
Since the question tags say we're constrained to C++14, we'd need to reformulate the constraint as a std::enable_if
:
template<typename T, typename U>
constexpr
std::enable_if_t<(std::is_lvalue_reference<U>::value
|| !std::is_lvalue_reference<T>::value)
&& std::is_convertible<std::remove_reference_t<U>*,
std::remove_reference_t<T>*>::value,
T&&>
forward(U&& u) noexcept
{
return static_cast<T&&>(u);
}
-
\$\begingroup\$ Well, the
std::forward
-replacement with two template-arguments you tried to fix up is now overly restrictive.struct { operator int&(){ return data; } int data; } x; (void)std::forward<int&>(x);
would no longer work. Yes, the example is contrived, but that's not the point. \$\endgroup\$Deduplicator– Deduplicator2022年01月22日 22:24:04 +00:00Commented Jan 22, 2022 at 22:24