5
\$\begingroup\$

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.

\$\endgroup\$
2
  • 5
    \$\begingroup\$ You should read n2951 by Howard Hinnant. \$\endgroup\$
    – Snowhawk
    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\$ Jul 7, 2017 at 16:26

1 Answer 1

1
\$\begingroup\$

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);
}
\$\endgroup\$
1
  • \$\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\$ Jan 22, 2022 at 22:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.