How to Make Template Rvalue Reference Parameter Only Bind to Rvalue Reference

How to make template rvalue reference parameter ONLY bind to rvalue reference?

You can restrict T to not be an lvalue reference, and thus prevent lvalues from binding to it:

#include <type_traits>

struct OwnershipReceiver
{
template <typename T,
class = typename std::enable_if
<
!std::is_lvalue_reference<T>::value
>::type
>
void receive_ownership(T&& t)
{
// taking file descriptor of t, and clear t
}
};

It might also be a good idea to add some sort of restriction to T such that it only accepts file descriptor wrappers.

Have rvalue reference instead of forwarding reference with variadic template

The way to have a bunch of rvalue references is with SFINAE:

template <class... Args,
std::enable_if_t<(!std::is_lvalue_reference<Args>::value && ...), int> = 0>
void foo(Args&&... args) { ... }

Fold-expressions are C++17, it is easy enough to write a metafunction to get that same behavior in C++14. This is your only option really - you want a constrained function template deducing to rvalue references, but the only available syntax is overloaded to mean forwarding references. We could make the template parameters non-deduced, but then you'd have to provide them, which seems like not a solution at all.

With concepts, this is of course cleaner, but we're not really changing the underlying mechanism:

template <class... Args>
requires (!std::is_lvalue_reference<Args>::value && ...)
void foo(Args&&... args) { ... }

or better:

template <class T>
concept NonReference = !std::is_lvalue_reference<T>::value;

template <NonReference... Args>
void foo(Args&&... ) { ... }

It's worth pointing out that neither of these work:

template <class... Args> auto foo(const Args&... args) = delete;
template <class... Args> auto foo(Args&... args) = delete;

because they only delete overloads that take all lvalue references, and you want to delete overloads that take any lvalue references.

Rvalue reference parameters and template functions

The behavior of deduction in template parameters is unique, and is the reason your template version works. I've explained exactly how this deduction works here, in the context of another question.

Summarized: when the argument is an lvalue, T is deduced to T&, and T& && collapses to T&. And with the parameter at T&, it is perfectly valid to supply an lvalue T to it. Otherwise, T remains T, and the parameter is T&&, which accepts rvalues arguments.

Contrarily, int&& is always int&& (no template deduction rules to coerce it to something else), and can only bind to rvalues.

How to pass a rvalue reference parameter to a template operator() function in C++?

Problem 1

One problem is at the line std::apply(m_function, all_args); where you are passing all_args as an lvalue to std::apply, which will pass it as an lvalue to func_1's third parameter, which will fail, because func_1's third parameter is an rvalue ref, which can't bind to an lvalue argument.

Indeed, changing that line to std::apply(m_function, std::move(all_args)); makes the first two // Compile Error lines actually compile and generate the correct output. Likewise, I would call std::move also on the other usage of all_args.

Problem 2

It looks like std::make_tuple(std::forward<NewArgs>(args)...); is not doing what you think it does. Changing it to std::tuple<NewArgs&&...>(std::forward<NewArgs>(args)...); solves the problem; that is equivalent to std::forward_as_tuple(std::forward<NewArgs>(args)...);.

The details of why this change works lie in the return types of std::make_tuple vs. std::forward_as_tuple: the latter returns a tuple of references, whereas the former returns a tuple of values which have been copied/moved from the arguments.

Now, follow my reasoning:

  • First, look at curried(Function function, std::tuple<CapturedArgs...> args): it takes the paramter args which should be of type std::tuple<CaptureArgs...>. Are we sure args has that type? Well, if template type deduction took place, it would be obvious that the answer is yes. However, the call to that constructor never takes advantange of type deduction, because the only call is in return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args); where the template arguments are explicitly provided.
  • So the question remains: is all_args of the type the construtor expects? Well, the template arguments CapturedArgs..., NewArgs... in the recursive call correspond to the class... CapturedArgs template parameters of the class, which are used to form the type of the argument of the constructor, std::tuple<CaptureArgs...>.
  • So the answer to this question is given by static_asserting, before the recursive return, that all_args is of type std::tuple<CapturedArgs..., NewArgs...>:
    static_assert(std::is_same_v<decltype(all_args), std::tuple<CapturedArgs..., NewArgs...>>);
    return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
  • Unfortunately you can't put this assertion in the code, as long as you pass values wrapped in std::ref/std::cref, because those fail the static_assertion but are still valid inputs, exactly because of how std::reference_wrappers work. You could write a more complicated assertion or you can momentarily change std::ref(bla) to bla and so on, and check that the static_assert I gave you passes when using std::forward_as_tuple and fails when using std::make_tuple.

Thanks for asking this question. It was a great opportunity for me to dive again into this complicated topic and finally understand it!

One more point

Above I suggested that you use std::forward_as_tuple(std::forward<NewArgs>(args)...);.

Well, probably this suggestion is wrong.

At page 238, the author explicitly states that he wants the tuple to store copies, to prevent scenarios where the curried function survives its arguments. Therefore, it's probably best to go with this instead (notice, there's no && in the template argument passed to std::tuple):

        auto new_args = std::tuple<NewArgs...>(std::forward<NewArgs>(args)...);

Why a template argument of a rvalue reference type can be bound to a lvalue type?

You can read this article Universal References in C++11 to understand. Here some part of it:

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

Widget&& var1 = someWidget;      // here, “&&” means rvalue reference

auto&& var2 = var1; // here, “&&” does not mean rvalue reference

template<typename T>
void f(std::vector<T>&& param); // here, “&&” means rvalue reference

template<typename T>
void f(T&& param); // here, “&&”does not mean rvalue reference

Here a relevant for your case excerpt from the Standard:

... function template parameter type (call it P) ... If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

How to have a templated function require its argument be passed by rvalue reference?

template<class T>
void f(T &&)
requires(!std::is_lvalue_reference_v<T>);

Overload rvalue and lvalue reference for template deduced type with return value and its implementation

Through the magic of reference collapsing...

 template<class T>
[[nodiscard]] std::decay_t<T> unique(T&& t) {
std::decay_t<T> vec=std::forward<T>(t);
std::sort( vec.begin(), vec.end() );
vec.erase(
std::unique( vec.begin(), vec.end() ),
vec.end()
);
return vec;
}

works for both cases. DRY.

Note that view types, like std span, have issues.

C++11: Why rvalue reference parameter implicitly converted to lvalue

One, the x argument to fn isn't an r-value reference, it's a "universal reference" (yes, this is rather confusing).

Two, the moment you give an object a name, that name is not an r-value unless explicitly "fixed", either with std::move (to make it an r-value reference, always), or with std::forward (to convert it back to its original type in the case of universal references). If you want to avoid the complaint, use std::forward to forward as the original type:

template <class T>
void fn (T&& x) {
overloaded(std::forward<T>(x));
}

Lvalue to rvalue reference binding

Insert(key, Value()); // Compiler error here

key here is Key&& key - this is an lvalue! It has a name, and you can take its address. It's just that type of that lvalue is "rvalue reference to Key".

You need to pass in an rvalue, and for that you need to use std::move:

Insert(std::move(key), Value()); // No compiler error any more

I can see why this is counter-intuitive! But once you distinguish between and rvalue reference (which is a reference bound to an rvalue) and an actual rvalue, it becomes clearer.

Edit: the real problem here is using rvalue references at all. It makes sense to use them in a function template where the type of the argument is deduced, because this allows the argument to bind to either an lvalue reference or an rvalue reference, due to reference collapsing rules. See this article and video for why: http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

However, in this case the type of Key is not deduced when the function is called, as it has already been determined by the class when you instantiated FastHash<std::string, ... >. Thus you really are prescribing the use of rvalue references, and thus using std::move fixes the code.

I would change your code to that the parameters are take by value:

template <typename Key, typename Value, typename HashFunction, typename Equals>
Value& FastHash<Key, Value, HashFunction, Equals>::operator[](Key key)
{
// Some code here...

Insert(std::move(key), Value());

// More code here.
}

template <typename Key, typename Value, typename HashFunction, typename Equals>
void FastHash<Key, Value, HashFunction, Equals>::Insert(Key key, Value value)
{
// ...
}

Don't worry too much about extra copies due to use of value arguments - these are frequently optimised out by the compiler.

lvalue binding to rvalue reference

Since T is a template argument, T&& becomes a forwarding-reference. Due to reference collapsing rules, f(T& &&) becomes f(T&) for lvalues and f(T &&) becomes f(T&&) for rvalues.



Related Topics



Leave a reply



Submit