Search code examples
c++pointersreferencerotationvariadic-templates

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


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.


Solution

  • 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.