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 paramterargs
which should be of typestd::tuple<CaptureArgs...>
. Are we sureargs
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 inreturn 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 argumentsCapturedArgs..., NewArgs...
in the recursive call correspond to theclass... 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_assert
ing, before the recursivereturn
, thatall_args
is of typestd::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 thestatic_assert
ion but are still valid inputs, exactly because of howstd::reference_wrapper
s work. You could write a more complicated assertion or you can momentarily changestd::ref(bla)
tobla
and so on, and check that thestatic_assert
I gave you passes when usingstd::forward_as_tuple
and fails when usingstd::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
Googletest: How to Skip a Test
Copying Std::Vector: Prefer Assignment or Std::Copy
Why Does This Call the Default Constructor
Difference Between Const. Pointer and Reference
Should I Inherit from Std::Exception
Difference Between C++11 Std::Bind and Boost::Bind
How to Estimate the Thread Context Switching Overhead
How to Find an Official Reference Listing the Operation of Sse Intrinsic Functions
General Use Cases for C++ Containers
Differencebetween an Empty and a Null Std::Shared_Ptr in C++
Constant Variables Not Working in Header
Why Can't Visual Studio Find My Dll
Acquire/Release Versus Sequentially Consistent Memory Order