Search code examples
c++language-lawyerperfect-forwardingc++23

Can you perfectly forward expressions in C++23?


In C++23, it became easier to perfectly forward prvalues thanks to auto() or auto{}. With this new tool, is it now possible to form a FORWARD(e) expression for an expression e with the following requirements?

  1. FORWARD(e) has the same type as e, disregarding reference qualifiers
  2. if decltype(e) is an lvalue/rvalue reference, then FORWARD(e) is an lvalue/xvalue respectively
  3. otherwise, FORWARD(e) has the same value category as e
  4. no additional copying or moving may take place

We can do imperfect forwarding with std::forward already:

#define FORWARD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)

This preserves the type and converts references appropriately. However, a prvalue will be turned into an xvalue, because std::forward returns an rvalue reference (i.e. FORWARD(e) is an xvalue after conversions).

As a consequence:

T x = T();          // copy elision because x is initialized to prvalue
T x = FORWARD(T()); // calls move constructor

Is it possible to do true perfect forwarding in C++, including preservation of prvalues?


Solution

  • You basically want FORWARD(e) to be e, except if e happens to name an rvalue reference variable, in which case you want move(e).

    You can simply cast to the type of decltype((e)) (which if e is a prvalue, will be elided), except when e is an rvalue reference variable, since decltype((e)) will be an lvalue reference.

    #include <type_traits>
    
    template<typename IdType, typename ExprType>
    using perfect_forward_cast = std::conditional_t<std::is_rvalue_reference_v<IdType>,
        IdType,
        ExprType
    >;
    
    #define FORWARD(...) ::perfect_forward_cast<decltype( __VA_ARGS__ ), decltype(( __VA_ARGS__ ))>( __VA_ARGS__ )
    

    https://godbolt.org/z/WYehMxzPb