如何将任意数量的输入输出参数旋转到一个函数(优雅地)



Input: 1, 2, 3, 4

Output: 2, 3, 4, 1

我的解决方案:使用我的代码

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

我想旋转任何数量的元素,如上所述。我的解决方案似乎奏效了,但在我看来,这不是很好;优雅";。我不喜欢我必须在这里初始化一个向量。有没有一种方法可以在没有矢量的情况下完成同样的事情?也许是递归?

理想情况下,我还希望传递引用而不是指针。

这里有一个不使用std::vector的错误解决方案,其中所有参数都是通过引用传递的,只需要复制一个元素:

// 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};
}

这是一个演示。


编辑:上面的解决方案很优雅,但不正确,因为分配给std::tuple成员的顺序似乎未指定。上面的代码依赖于从左到右对std::tie的参数进行赋值,因此解决方案不起作用。

这里有一个使用std::apply的更详细的解决方案,它保证按顺序调用传入元组的参数:

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

虽然这更为详细,但与第一个执行1个副本构造和N个副本分配的解决方案不同,该解决方案的优点是只执行1个复制构造和N次移动分配。据我所知,第一种解决方案不可能做到这一点。显然,这是正确的,这也是一个很大的优势:(

这里有一个演示,也显示了复制/移动。


这是@max66给出的一个更简单的解决方案,它也与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);
}

这是一个演示。

没有std::vector但使用std::reference_wrapper的模板折叠的另一种用途(因此需要C++17或更新版本(

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

在C++11/C++14中,您可以模拟初始化C样式阵列的模板折叠

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

如果您可以使用C++17…那么使用如下模板折叠呢?

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++]) );
}

也适用于参考

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++]) );
}

但你必须用不同的方式来称呼它(显然(

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

最新更新