Search code examples
c++c++11rvalue-reference

Performance comparison: f(std::string&&) vs f(T&&)


I'm trying to understand the performance implications of using WidgetURef::setName (URef being a Universal Reference, the term coined by Scott Meyers) vs WidgedRRef::setName (RRef being an R-value Reference):

#include <string>

class WidgetURef {
    public:
    template<typename T>
    void setName(T&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

class WidgetRRef {
    public:
    void setName(std::string&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

int main() {
    WidgetURef w_uref;
    w_uref.setName("Adela Novak");

    WidgetRRef w_rref;
    w_rref.setName("Adela Novak");
}

I do appreciate that with universal references one should be using std::forward instead, but this is just an (imperfect) example to highlight the interesting bit.

Question In this particular example, what is the performance implications of using one implementation vs the other? Although WidgetURef requires type deduction, it's otherwise identical to WidgetRRef, isn't it? At least in this particular scenario, in both cases the argument is an r-value reference, so no temporaries are created. Is this reasoning correct?

Context The example was taken from Item25 of Scott Meyers' "Effective Modern C++" (p. 170). According to the book (provided that my understanding is correct!), the version taking a universal reference T&& doesn't require temporary objects and the other one, taking std::string&&, does. I don't really see why.


Solution

  • In this particular example, what is the performance implications of using one implementation vs the other?

    Universal references, as Scott Meyers calls them, are not primarily there for performance reasons, but, loosely speaking, to treat both L- and Rvalue references in the same manner to avoid countless overloads (and for being able to propagate all type information during forwarding).

    [...] so no temporaries are created. Is this reasoning correct?

    Rvalue references do not prevent temporaries from being created. Rvalue references are the kind of references that are able to be bound to temporaries (apart from const lvalue references)! Of course, in your example, there will be temporaries, but the rvalue reference can bind to it. The universal reference first has to undergo the reference collapsing but in the end, the behaviour will be identical in your case:

    // explicitly created temporary
    w_uref.setName(std::string("Adela Novak")); 
    // will create temporary of std::string --> uref collapses to rvalue ref
    // so is effectively the same as
    w_rref.setName("Adela Novak");
    

    By using the rvalue reference on the other hand, you force a temporary implicitly as std::string&& cannot bind to that literal.

    w_rref.setName("Adela Novak"); // need conversion
    

    So the compiler will create a temporary std::string from the literal the rvalue reference then can bind to.

    I don't really see why.

    In this case, the template will be resolved to const char(&)[12] and thus, no std::string temporary will be created in contrast to the case above. This therefore is more efficient.