How to rotate an arbitrary number of in-out parameters to a function (elegantly)?

Viewed 135

Input: 1, 2, 3, 4

Output: 2, 3, 4, 1

My solution: play with my code

template <typename T, typename ... Param>
void rotate(T* first, Param* ... params) {
    std::vector<T*> tmp = {first, params...};
    if (tmp.size() <= 1) {return;}
    T f = *first;
    for (size_t i = 1; i < tmp.size(); ++i) {
        *tmp.at(i - 1) = *tmp.at(i);
    }
    *tmp.at(tmp.size() - 1) = f;
}

I would like to rotate any number of elements as described above. My solution seems to work, but in my eyes, it's not very "elegant". I do not like that I have to initialize a vector here. Is there a way to accomplish the same thing without the vector? Maybe with recursion?

Ideally, I would also like to pass references instead of pointers.

3 Answers

Here's an INCORRECT solution without using std::vector, where all arguments are passed by reference, and only a single element needs to be copied:

// THIS IS WRONG, SEE EDIT BELOW

template<typename T, typename ...Ts>
void rotate(T& first, Ts& ...rest) 
{
   auto first_copy = first; 
   std::tie(first, rest...) = {rest..., first_copy};
}

Here's a demo.


Edit: The above solution is elegant, but is incorrect, since it appears that the order of assignments to std::tuple members is unspecified. The code above relies on the assignments to the arguments to std::tie to be done from left to right, so the solution doesn't work.

Here's a more verbose solution using std::apply, which is guaranteed to invoke the arguments of the passed in tuple in order:

template<typename T, typename ...Ts>
void rotate(T& first, Ts& ...rest) 
{
    auto first_copy = first;

    std::apply([&](auto&... lhs) {
        std::apply([&](auto&... rhs) {
            ((lhs = std::move(rhs)), ...); 
        }, std::tuple<T&, Ts&...>{rest..., first_copy});
    }, std::tuple<T&, Ts&...>{first, rest...});
}

While this is more verbose, unlike the first solution which does 1 copy-construction and N copy-assignments, this solution has the advantage that it only does 1 copy-construction and N move-assignments. This is not possible with the first solution, as far as I can tell. And obviously, it's correct, which is a big advantage too :)

Here's a demo that also shows the copies/moves made.


Here's an even simpler solution given by @max66, which is also as efficient as the solution with std::apply:

template<typename T, typename ...Ts>
void rotate(T& first, Ts& ...rest) 
{
  T first_copy{first}; 

  [&](auto& first_ref, auto & ... rest_ref) { 
      first = std::move(first_ref);
      (..., (rest = std::move(rest_ref))); 
  } (rest..., first_copy);
}

Here's a demo.

Another use of template folding (so C++17 or newer is needed) without std::vector but using std::reference_wrapper

template <typename H, typename ... Ts>
void rotate (H & head, Ts & ... tail) {
    H h0 { head };
    std::reference_wrapper<H>  rw { head };

    ( (rw.get() = tail, rw = tail), ..., (rw.get() = h0) );
}

In C++11/C++14 you can simulate template folding initializing a C-style array

template <typename H, typename ... Ts>
void rotate (H & head, Ts & ... tail) {
    using unused = int[];
    H h0 { head };
    std::reference_wrapper<H>  rw { head };
    
    (void)unused { 0, (rw.get() = tail, rw = tail, 0)... };

    rw.get() = h0;
}

If you can use C++17... what about using template folding as follows ?

template <typename T, typename ... Param>
void rotate(T* first, Param* ... params) {
    std::array<T, 1u+sizeof...(params)> tmp = {*params..., *first};

    std::size_t  index {};

    ( (*first = tmp[index++]), ..., (*params = tmp[index++]) );
}

Works also with references

template <typename T, typename ... Param>
void rotate(T & first, Param & ... params) {
    std::array<T, 1u+sizeof...(params)> tmp = {params..., first};

    std::size_t  index {};

    ( (first = tmp[index++]), ..., (params = tmp[index++]) );
}

but you have to call it differently (obviously)

rotate(i, j, k, l, m, n);
Related