How do I create a variable template function to shift argument values ββand handle both lvalues ββand rvalues?
I want to replace these macros with a variation pattern that achieves the same.
#define SHFT2( a, b, c ) do { (a) = (b); (b) = (c); } while(0)
#define SHFT3( a, b, c, d ) do { (a) = (b); (b) = (c); (c) = (d); } while(0)
#define SHFT4( a, b, c, d, e ) do { (a) = (b); (b) = (c); (c) = (d); (d) = (e); } while(0)
I have a solution that works for lvalues
template<typename T, typename... Ts>
T first(T t, Ts... ts)
{
return t;
}
template<typename T>
void shift(T t)
{
// do nothing
}
template<typename T, typename... Ts>
void shift(T& t, Ts&... ts)
{
t = first(ts...);
shift(ts...);
}
For example this works
int w = 1;
int x = 2;
int y = 3;
int z = 4;
shift(w, x, y, z);
printf("%d %d %d %d\n", w, x, y, z); // 2 3 4 4
But I want to be able to change the r value at the end
shift(w, x, y, z, 5);
printf("%d %d %d %d\n", w, x, y, z); // expect 2 3 4 5
I am getting this error
test.cpp:31:2: error: no matching function for call to 'shift'
shift(w, x, y, z, 5);
^~~~~
test.cpp:16:6: note: candidate function [with T = int, Ts = <int, int, int, int>] not viable: expects an l-value for 5th
argument
void shift(T& t, Ts&... ts)
^
test.cpp:10:6: note: candidate function template not viable: requires single argument 't', but 5 arguments were provided
void shift(T t)
because you cannot reference rvalues.
How can I make this work in both cases?
source to share
You can use a reference forwarding parameter to accept both lvalues ββand rvalues, and std::forward
to "forward" the category of the original argument's values, ie. convert the parameter to the appropriate match category.
template <typename T>
void shift(T&& t) {
// do nothing
}
template<typename T1, typename T2, typename... Ts>
void shift(T1&& t1, T2&& t2, Ts&&... ts) {
std::forward<T1>(t1) = std::forward<T2>(t2);
shift(std::forward<T2>(t2), std::forward<Ts>(ts)...);
}
It std::forward<T1>(t1)
guarantees to t1
be assigned as an lvalue if the argument was an lvalue and as an rvalue if the argument was an rvalue. For example, shift(42, x)
will not compile because you cannot assign a value to r of type int
.
std::forward<T2>(t2)
ensures that if the argument for t2
was an lvalue it will be copied, and if it was an rvalue it will be moved if possible.
std::forward<T2>(t2)
and std::forward<Ts>(ts)...
pass the value category information to the recursive call.
source to share
You need something like
#include <utility>
// abort
template <class T> void shift(T&&) { }
// assign to lvalue
template<class T1, class T2, class... Ts>
void shift(T1& t1, T2&& t2, Ts&&... ts)
{
t1 = std::forward<T2>(t2);
shift(std::forward<T2>(t2), std::forward<Ts>(ts)...);
}
which expects all parameters but the latter to be lvalues, where the last can also be an lvalue.
- When all parameters except the last one are destroyed, an interrupt is called
shift(T&&)
. - All other parameters are passed as
t1
at some point, which is an lvalue reference, ensuring that the lvalues ββare passed in any slot, but the last one.
i.e. shift(w, x, y, z, 5);
compiles but shift(w, x, y, 5, z);
or shift(w, x, y, 5, 5);
doesn't work.
source to share
because you cannot reference rvalues.
You are specifying the parameter as an lvalue reference, just change them to forwarders , which serve as both an lvalue reference and an rvalue reference, according to the pass-in argument's value category. eg.
template<typename T, typename... Ts>
T&& first(T&& t, Ts&&... ts)
{
return std::forward<T>(t);
}
template<typename T>
void shift(T&& t)
{
// do nothing
}
template<typename T, typename... Ts>
void shift(T&& t, Ts&&... ts)
{
t = first(std::forward<Ts>(ts)...);
shift(std::forward<Ts>(ts)...);
}
source to share