Search code examples
c++movetemplate-argument-deduction

What should be preferred, moving or forwarding arguments


Below is simplified example of the templated List, where are two append_move() and append_forward() functions that have the same goal, take the arguments and emplace them in to the container List. The first append_move() function takes arg1 passed by value and then moves it to the emplace_back() function. The second append_move() function uses autodeduction of arg1 and then forwards it to the emplace_back() function.

Does the append_forward() function have any advantages over the append_move() function and which function should be preferred?

#include <deque>
#include <string>
#include <utility>

template<typename T>
class List
{
    public:
        void append_move(T arg1, std::string arg2)
        {
            list.emplace_back(std::move(arg1), std::move(arg2));
        }

        template<typename X>
        void append_forward(X&& arg1, std::string arg2)
        {
            list.emplace_back(std::forward<X>(arg1), std::move(arg2));
        }

    private:
        std::deque<std::pair<T, std::string>> list;
};

Solution

  • Forward or move

    If T's destructor can't be optimized out and produces visible side-effects, e.g.

    struct Loud { ~Loud() { std::cout << "destructor\n"; } };
    

    then

    List<Loud> list;
    Loud const loud;
    list.append_forward(loud);
    

    calls one less destructor than

    list.append_move(loud);
    

    because the latter constructs one more object => has to call one more destructor. So, forwarding is more preferable.

    However, it makes the API less pretty:

    another_list.append_move({"some", "arguments"}); // works and pretty
    //another_list.append_forward({"some", "arguments"}); // doesn't compile
    another_list.append_forward(Foo{"some", "arguments"}); // works
    

    Overload by hand

    So, unfortunately, providing hand-written overloads seems to be the best solution (for the user of your code) out of these three:

    void append_overload(T&& t); // TODO actually implement
    void append_overload(T const& t) { append_overload(T{t}); }
    

    Emplace

    However (once again), if you care about all of this, consider emplacing:

    template<typename... As> void append_emplace(As&&... args) {
      list.emplace_back(std::forward<As>(args)...);
    }
    
    // works and pretty as long as you don't need to pass-construct two arguments
    another_list.append_emplace("some", "arguments");
    

    Other overloads

    Besides, if you have more append_* overloads, note that forwarding/emplacing versions are less prioritized because of being templates. The choice is yours.