Search code examples
c++c++11move-semanticsperfect-forwardingtemplate-argument-deduction

How to omit perfect forwarding for deduced parameter type?


Let's say I have some function a parameter type (or several parameter types) of type which I want to be deduced. Also I want different behavior based on the fact is it rvalue or lvalue. Straightforwardly writing it leads to an obvious (for experienced people) trap because of perfect forwarding:

#include <iostream>
#include <vector>

template <typename T>
void f (T &&v) // thought to be rvalue version
{
   // some behavior based on the fact that v is rvalue
   auto p = std::move (v);
   (void) p;
}

template <typename T>
void f (const T &v) // never called
{  
   auto p = v;
   (void) p;
}

int main ()
{
    std::vector<int> x = {252, 135};
    auto &z = x;
    f (z);
    std::cout << x.size () << '\n'; // woah, unexpected 0 or crash
}

Even though sneaky nature of such behavior is already an interesting point but my question is actually different - what is good, concise, understandable workaround for such situation?

If perfectly forwarded type is not deduced (e.g. it's already known template parameter of an outer class or something like this) there's well known workaround using typename identity<T>::type&& instead of T&& but since the same construction is a workaround for avoiding type deduction it doesn't help in this case. I could probably imagine some sfinae tricks to resolve it but code clarity would probably be destroyed and it will look completely different from the similar non-template functions.


Solution

  • SFINAE hidden in a template parameter list:

    #include <type_traits>
    
    template <typename T
            , typename = typename std::enable_if<!std::is_lvalue_reference<T>{}>::type>
    void f(T&& v);
    
    template <typename T>
    void f(const T& v);
    

    DEMO


    SFINAE hidden in a return type:

    template <typename T>
    auto f(T&& v)
        -> typename std::enable_if<!std::is_lvalue_reference<T>{}>::type;
    
    template <typename T>
    void f(const T& v);
    

    DEMO 2


    In typename std::enable_if<!std::is_lvalue_reference<T>{}>::type can be shortened to:

    std::enable_if_t<!std::is_lvalue_reference<T>{}> 
    

    Anyway, even in you can shorten the syntax with an alias template if you find it more concise:

    template <typename T>
    using check_rvalue = typename std::enable_if<!std::is_lvalue_reference<T>{}>::type;
    

    DEMO 3


    With constexpr-if:

    template <typename T>
    void f(T&& v)
    {
        if constexpr (std::is_lvalue_reference_v<T>) {}
        else {}
    }
    

    With concepts:

    template <typename T>
    concept rvalue = !std::is_lvalue_reference_v<T>;
    
    void f(rvalue auto&& v);
    
    void f(const auto& v);
    

    DEMO 4