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