如何(优雅地)将任意数量的输入输出参数旋转到函数?
How to rotate an arbitrary number of in-out parameters to a function (elegantly)?
Input: 1, 2, 3, 4
Output: 2, 3, 4, 1
我的解决方案: 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;
}
我想旋转任意数量的元素,如上所述。我的解决方案似乎有效,但在我看来,它不是很“优雅”。我不喜欢我必须在这里初始化一个向量。有没有办法在没有向量的情况下完成同样的事情?也许用递归?
理想情况下,我也想传递引用而不是指针。
如果你可以使用 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);
这是一个不正确的解决方案,没有使用 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};
}
这里是 demo。
编辑:上述解决方案很优雅,但不正确,因为似乎未指定 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 copy-construction 和 N copy-assignments 不同,此解决方案的优点是它只执行 1 copy-construction 和 N move-assignments。据我所知,第一个解决方案是不可能的。显然,这是正确的,这也是一个很大的优势:)
这里有一个 demo,它也显示了 copies/moves 的制作。
这是@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);
}
这是 demo。
模板折叠的另一种用法(因此需要 C++17 或更新版本)没有 std::vector
但使用 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) );
}
在 C++11/C++14 中你可以模拟模板折叠初始化一个 C-style 数组
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;
}
Input: 1, 2, 3, 4
Output: 2, 3, 4, 1
我的解决方案: 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;
}
我想旋转任意数量的元素,如上所述。我的解决方案似乎有效,但在我看来,它不是很“优雅”。我不喜欢我必须在这里初始化一个向量。有没有办法在没有向量的情况下完成同样的事情?也许用递归?
理想情况下,我也想传递引用而不是指针。
如果你可以使用 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);
这是一个不正确的解决方案,没有使用 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};
}
这里是 demo。
编辑:上述解决方案很优雅,但不正确,因为似乎未指定 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 copy-construction 和 N copy-assignments 不同,此解决方案的优点是它只执行 1 copy-construction 和 N move-assignments。据我所知,第一个解决方案是不可能的。显然,这是正确的,这也是一个很大的优势:)
这里有一个 demo,它也显示了 copies/moves 的制作。
这是@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);
}
这是 demo。
模板折叠的另一种用法(因此需要 C++17 或更新版本)没有 std::vector
但使用 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) );
}
在 C++11/C++14 中你可以模拟模板折叠初始化一个 C-style 数组
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;
}