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

Solution 1:

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.

Solution 2:

A simple way is to provide a deleted member which accepts an lvalue reference:

template<typename T> void receive_ownership(T&) = delete;

This will always be a better match for an lvalue argument.


If you have a function that takes several arguments, all of which need to be rvalues, we will need several deleted functions. In this situation, we may prefer to use SFINAE to hide the function from any lvalue arguments.

One way to do this could be with C++17 and the Concepts TS:

#include <type_traits>

template<typename T>
void receive_ownership(T&& t)
    requires !std::is_lvalue_reference<T>::value
{
     // taking file descriptor of t, and clear t
}

or

#include <type_traits>

void receive_ownership(auto&& t)
    requires std::is_rvalue_reference<decltype(t)>::value
{
     // taking file descriptor of t, and clear t
}

Going slightly further, you're able to define a new concept of your own, which may be useful if you want to reuse it, or just for extra clarity:

#include <type_traits>

template<typename T>
concept bool rvalue = std::is_rvalue_reference<T&&>::value;


void receive_ownership(rvalue&& t)
{
     // taking file descriptor of t, and clear t
}

Note: with GCC 6.1, you'll need to pass -fconcepts to the compiler, as it's an extension to C++17 rather than a core part of it.

Just for completeness, here's my simple test:

#include <utility>
int main()
{
    int a = 0;
    receive_ownership(a);       // error
    receive_ownership(std::move(a)); // okay

    const int b = 0;
    receive_ownership(b);       // error
    receive_ownership(std::move(b)); // allowed - but unwise
}

Solution 3:

I learnt something that seems to confuse people quite often: using SFINAE is OK, but I can't use:

std::is_rvalue_reference<T>::value

The only way it works as I want is

!std::is_lvalue_reference<T>::value

The reason is: I need my function to receive an rvalue, not an rvalue reference. A function conditionally enabled with std::is_rvalue_reference<T>::value will not receive an rvalue, but rather receives an rvalue reference.

Solution 4:

For lvalue references, T is deduced to be an lvalue reference, and for rvalue references, T is deduced to be a non-reference.

So if the function binds to a rvalue reference, what is seen at the end by the compiler for a certain type T is:

std::is_rvalue_reference<T>::value

and not

std::is_rvalue_reference<T&&>::value