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?

+3


source to share


3 answers


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.

+1


source


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.

+1


source


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)...);
}

      

+1


source







All Articles