Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 300
Predelnik Avatar asked Aug 12 '15 17:08

Predelnik


People also ask

What is perfect forwarding?

If a function templates forward its arguments without changing its lvalue or rvalue characteristics, we call it perfect forwarding. Great. But what are lvalues and rvalues?

What is the forward template in C++?

One can see forward as a pretty wrapper around static_cast<T&&> (t) when T can be deduced to either U& or U&&, depending on the kind of argument to the wrapper (lvalue or rvalue). Now we get wrapper as a single template that handles all kinds of forwarding cleanly. The forward template exists in C++11, in the <utility> header, as std::forward.

Can a function parameter be bound to a reference parameter?

Rvalues cannot be bound to function parameters that are references, so the following completely reasonable calls will now fail: And no, making those reference parameters const won't cut it either, because func may legitimately want to accept non- const reference parameters.

How do you deduce the type of a paramtype?

The simplest situation is when ParamType is a reference type or a pointer type, but not a universal reference. In that case, type deduction works like this: If expr ’s type is a reference, ignore the reference part. Then pattern-match expr ’s type against ParamType to determine T. the deduced types for param and T in various calls are as follows:


1 Answers

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 c++14 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 c++11 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 c++17 constexpr-if:

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

With c++20 concepts:

template <typename T>
concept rvalue = !std::is_lvalue_reference_v<T>;

void f(rvalue auto&& v);

void f(const auto& v);

DEMO 4

like image 176
Piotr Skotnicki Avatar answered Sep 20 '22 04:09

Piotr Skotnicki