Search code examples
c++perfect-forwarding

Do I need to perfect forward arguments in these cases where the arguments are used directly in the function?


I thought to myself that I don't need std::forward<T>(arg); in my function because I wasn't passing the argument on to another function, I was using it directly. However then I thought even if I use it directly, by for example assigning it, or using it as a constructor argument then those each are function calls, respectively to operator= and constructor, which is a function call:

template <typename element_T>
    void push_back(element_t&& copy)
    {
        *_end = copy; // This calls operator=, which is a function
    }

template <typename ... ConstructorArgs>
void emplace_back(ConstructorArgs&& ... args) 
    { 
        
        new (_end) element_t(args); 
// Calls constructor, needs (std::forward<ConstructorArgs>(args)...) ?

        
    }

Do I need the calls to std::forward in these cases?


Solution

  • An expression that is a name of a variable is always an lvalue. For example, in

    1  template <typename T>
    2  void push_back(T&& copy) {
    3      *_end = copy;
    4  }
    

    copy in line 3 has the lvalue value category no matter what type is deduced for T. Depending on how operator= in line 3 is defined/overloaded, this might result in selecting a wrong overload or in wrong type deduction.

    Consider the following simple example (that follows the Ted Lyngmo's example from the comments section):

    struct A {
        void operator=(const A&);  // (1)
        void operator=(A&&);       // (2)
    };
    
    struct C {
        template<class T>
        void push_back(T&& copy) {
            a = copy;
        }
        A a;
    };
    
    C{}.push_back(A{});
    

    Which A's assignment operator will be invoked here? One might expect (2) because A{} is a prvalue, but the correct answer is (1), because copy is an lvalue, and lvalues can't bind to rvalue references.

    If we change the assignment to

    a = std::forward<T>(copy);
    

    then the expression std::forward<T>(copy) will have the xvalue value category and the (2) assignment operator will be called, as expected.

    Placement new follows the same reasoning.