如何为 C++ 元组编写折叠/求和函数?
how to write a fold /sum function for C++ tuple?
我想为 std::tuple
编写一个 fold
函数,可以计算例如给定元组中所有元素的总和(或乘积)。例如,给定
std::tuple<int,double> t = std::make_tuple(1,2);
我想计算
auto s = sumT(t); //giving 3
我试过但无法编译下面的模板编程 (c++11/1z) 代码。我还尝试为我的其他问题 () 调整已接受的答案,但无法弄清楚如何在这种情况下使用 std::index_sequence
。
我遇到的问题是:
1) 我无法弄清楚类型,例如如何使用第一个元素的类型作为 return 类型。目前,我在模板中使用 _res
类型,但我不知道这是否会阻止 c++ 的自动类型推断。
2) 我想在不使用显式初始元素 0
的情况下对其进行编程,以便它可以用于其他类型的 fold
操作。
目前,递归在最后一个元素处结束。我想在 _size - 1
处结束递归,这样我就可以直接对最后一个元素执行操作,而无需求助于 0
.
我下面的代码试图通过递归来做到这一点。但是我不太了解模板编程,也不太了解循环如何为元组工作。
有人可以帮助修复代码或提出更好的解决方案吗?
到目前为止我的代码是:
#include <tuple>
#include <iostream>
#include <functional>
// helper class for fold operations
template<typename Op,typename _res, typename _Tp, size_t _i, size_t _size>
struct _tuple_fold {
static constexpr _res _op(Op const & op, const _Tp& _t) {
return _res(op(std::get<_i>(_t),
_tuple_fold<Op, _res, _Tp, _i + 1, _size>::_op(op,_t) ));
}
};
template<typename Op,typename _res,typename _Tp, size_t _size>
struct _tuple_fold<Op, _res,_Tp, _size, _size> {
static constexpr _res _op(Op const &, const _Tp&) { return 0; }
};
template <typename ... Ts>
auto sumT (std::tuple<Ts...> const & t1) {
return _tuple_fold::_op(std::plus<>{}, t1);
}
int main () {
std::tuple<int,double> t = std::make_tuple(1,2);
auto s = sumT(t);
std::cout << s << std::endl;
}
使用g++ -std=c++17 tuple_sum.cpp
编译的错误信息:
tuple_sum.cpp: In function ‘auto sumT(const std::tuple<_Elements ...>&)’:
tuple_sum.cpp:21:10: error: ‘template<class Op, class _res, class _Tp, long unsigned int _i, long unsigned int _size> struct _tuple_fold’ used without template parameters
return _tuple_fold::_op(std::plus<>{}, t1);
^
tuple_sum.cpp: In function ‘int main()’:
tuple_sum.cpp:27:19: error: ‘void s’ has incomplete type
auto s = sumT(t);
^
我不确定如何在调用站点上为 _tuple_fold
提供类型参数,尤其是 std::plus
.
的类型
值得庆幸的是,C++17 中的 if constexpr
允许我们通过引入部分专用的辅助结构来避免使事情复杂化,并且可以在我们想要的任何条件下轻松终止递归:
#include <functional>
#include <iostream>
#include <tuple>
#include <type_traits>
template <size_t index, class Op, class... Ts>
constexpr auto tuple_fold(Op op, const std::tuple<Ts...>& t) {
if constexpr(index == sizeof...(Ts) - 1) {
return std::get<index>(t);
} else {
return op(std::get<index>(t), tuple_fold<1 + index>(op, t));
}
}
template <typename ... Ts>
constexpr auto sumT (std::tuple<Ts...> const & t1) {
return tuple_fold<0>(std::plus<>{}, t1);
}
int main () {
std::tuple<int,double> t = {1, 2.0};
auto s = sumT(t);
static_assert(std::is_same_v<decltype(s), double>);
std::cout << s << std::endl;
}
科里鲁 link: http://coliru.stacked-crooked.com/a/1e7051b8652fb942
这将执行右折叠,a + (b + (c + ...))
但如果需要,可以很容易地重写它以执行左折叠。
请注意,在 c++17 中我们可以 apply() 和 fold:
auto t = std::make_tuple( 1, 2. );
auto sum = std::apply([]( auto... v ){ return ( v + ... ); }, t );
这适用于任何类似元组的类型,并遵循通常的 promotion/conversion 开箱即用的“+”规则(这可能是合意的,也可能不是合意的)。在上面我按值传递,因为我们正在处理算术类型,但是你当然可以应用你喜欢的转发策略...
我们可以利用 c++17 的左右折叠内置函数来折叠任何二进制操作。
template<class F, class Lhs=void>
struct invoke_by_times_t {
F& f;
Lhs lhs;
template<class Rhs>
auto operator*( Rhs&& rhs )&&
->invoke_by_times_t<F, std::invoke_result_t< F&, Lhs, Rhs >>
{
return {
f,
f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
};
}
};
template<class F>
struct invoke_by_times_t<F, void> {
F& f;
template<class Rhs>
invoke_by_times_t<F, Rhs> operator*( Rhs&& rhs )&&{
return {f, std::forward<Rhs>(rhs)};
}
};
template<class F>
auto fold_over( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args)mutable{
return ( invoke_by_times_t<F>{f}*...*decltype(args)(args) ).lhs;
};
}
现在给定任何二元函数,我们可以创建一个函数对象,在不进行任何递归的情况下折叠它。
与 std::apply
一起完成。
template <typename ... Ts>
auto sumT (std::tuple<Ts...> const & t1) {
return std::apply( fold_over(std::plus<>{}), t1);
}
这是左折。右折叠只涉及更改 fold_over
函数。如果您尝试将空包传递给它,它将无法编译。如果你传递给它一个元素,它会return那个元素。
#include <tuple>
#include <utility>
namespace detail {
template<class F, class T>
struct foldable : std::pair<const F&, T> {
using std::pair<const F&, T>::pair;
template<class V>
constexpr decltype(auto) operator&&(foldable<F, V>&& x) {
return detail::foldable {
this->first,
this->first(this->second, x.second),
};
}
};
template<class F, class T> foldable(const F&, T&&) -> foldable<F, T>;
}
// Folds left a parameter pack
template<class F, class... Args>
constexpr decltype(auto) fold_left_pack(const F& f, Args&&... args) {
static_assert(sizeof...(Args) > 0, "Cannot fold an empty pack.");
return (... && detail::foldable { f, std::forward<Args>(args) }).second;
}
// Folds right a parameter pack
template<class F, class... Args>
constexpr decltype(auto) fold_right_pack(const F& f, Args&&... args) {
static_assert(sizeof...(Args) > 0, "Cannot fold an empty pack.");
return (detail::foldable { f, std::forward<Args>(args) } && ...).second;
}
// Folds left a tuple
template<class F, class Tuple>
constexpr decltype(auto) fold_left(const F& f, Tuple&& tuple) {
return std::apply(
[&](auto&&... args) -> decltype(auto) {
return fold_left_pack(f, std::forward<decltype(args)>(args)...);
},
std::forward<Tuple>(tuple));
}
// Folds right a tuple
template<class F, class Tuple>
constexpr decltype(auto) fold_right(const F& f, Tuple&& tuple) {
return std::apply(
[&](auto&&... args) -> decltype(auto) {
return fold_right_pack(f, std::forward<decltype(args)>(args)...);
},
std::forward<Tuple>(tuple));
}
一个测试:
constexpr auto divide = [](auto x, auto y) { return x / y; };
constexpr auto tuple = std::make_tuple(8, 4, 2);
static_assert(1 == fold_left(divide, tuple));
static_assert(4 == fold_right(divide, tuple));
我想为 std::tuple
编写一个 fold
函数,可以计算例如给定元组中所有元素的总和(或乘积)。例如,给定
std::tuple<int,double> t = std::make_tuple(1,2);
我想计算
auto s = sumT(t); //giving 3
我试过但无法编译下面的模板编程 (c++11/1z) 代码。我还尝试为我的其他问题 (std::index_sequence
。
我遇到的问题是:
1) 我无法弄清楚类型,例如如何使用第一个元素的类型作为 return 类型。目前,我在模板中使用 _res
类型,但我不知道这是否会阻止 c++ 的自动类型推断。
2) 我想在不使用显式初始元素 0
的情况下对其进行编程,以便它可以用于其他类型的 fold
操作。
目前,递归在最后一个元素处结束。我想在 _size - 1
处结束递归,这样我就可以直接对最后一个元素执行操作,而无需求助于 0
.
我下面的代码试图通过递归来做到这一点。但是我不太了解模板编程,也不太了解循环如何为元组工作。
有人可以帮助修复代码或提出更好的解决方案吗?
到目前为止我的代码是:
#include <tuple>
#include <iostream>
#include <functional>
// helper class for fold operations
template<typename Op,typename _res, typename _Tp, size_t _i, size_t _size>
struct _tuple_fold {
static constexpr _res _op(Op const & op, const _Tp& _t) {
return _res(op(std::get<_i>(_t),
_tuple_fold<Op, _res, _Tp, _i + 1, _size>::_op(op,_t) ));
}
};
template<typename Op,typename _res,typename _Tp, size_t _size>
struct _tuple_fold<Op, _res,_Tp, _size, _size> {
static constexpr _res _op(Op const &, const _Tp&) { return 0; }
};
template <typename ... Ts>
auto sumT (std::tuple<Ts...> const & t1) {
return _tuple_fold::_op(std::plus<>{}, t1);
}
int main () {
std::tuple<int,double> t = std::make_tuple(1,2);
auto s = sumT(t);
std::cout << s << std::endl;
}
使用g++ -std=c++17 tuple_sum.cpp
编译的错误信息:
tuple_sum.cpp: In function ‘auto sumT(const std::tuple<_Elements ...>&)’:
tuple_sum.cpp:21:10: error: ‘template<class Op, class _res, class _Tp, long unsigned int _i, long unsigned int _size> struct _tuple_fold’ used without template parameters
return _tuple_fold::_op(std::plus<>{}, t1);
^
tuple_sum.cpp: In function ‘int main()’:
tuple_sum.cpp:27:19: error: ‘void s’ has incomplete type
auto s = sumT(t);
^
我不确定如何在调用站点上为 _tuple_fold
提供类型参数,尤其是 std::plus
.
值得庆幸的是,C++17 中的 if constexpr
允许我们通过引入部分专用的辅助结构来避免使事情复杂化,并且可以在我们想要的任何条件下轻松终止递归:
#include <functional>
#include <iostream>
#include <tuple>
#include <type_traits>
template <size_t index, class Op, class... Ts>
constexpr auto tuple_fold(Op op, const std::tuple<Ts...>& t) {
if constexpr(index == sizeof...(Ts) - 1) {
return std::get<index>(t);
} else {
return op(std::get<index>(t), tuple_fold<1 + index>(op, t));
}
}
template <typename ... Ts>
constexpr auto sumT (std::tuple<Ts...> const & t1) {
return tuple_fold<0>(std::plus<>{}, t1);
}
int main () {
std::tuple<int,double> t = {1, 2.0};
auto s = sumT(t);
static_assert(std::is_same_v<decltype(s), double>);
std::cout << s << std::endl;
}
科里鲁 link: http://coliru.stacked-crooked.com/a/1e7051b8652fb942
这将执行右折叠,a + (b + (c + ...))
但如果需要,可以很容易地重写它以执行左折叠。
请注意,在 c++17 中我们可以 apply() 和 fold:
auto t = std::make_tuple( 1, 2. );
auto sum = std::apply([]( auto... v ){ return ( v + ... ); }, t );
这适用于任何类似元组的类型,并遵循通常的 promotion/conversion 开箱即用的“+”规则(这可能是合意的,也可能不是合意的)。在上面我按值传递,因为我们正在处理算术类型,但是你当然可以应用你喜欢的转发策略...
我们可以利用 c++17 的左右折叠内置函数来折叠任何二进制操作。
template<class F, class Lhs=void>
struct invoke_by_times_t {
F& f;
Lhs lhs;
template<class Rhs>
auto operator*( Rhs&& rhs )&&
->invoke_by_times_t<F, std::invoke_result_t< F&, Lhs, Rhs >>
{
return {
f,
f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
};
}
};
template<class F>
struct invoke_by_times_t<F, void> {
F& f;
template<class Rhs>
invoke_by_times_t<F, Rhs> operator*( Rhs&& rhs )&&{
return {f, std::forward<Rhs>(rhs)};
}
};
template<class F>
auto fold_over( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args)mutable{
return ( invoke_by_times_t<F>{f}*...*decltype(args)(args) ).lhs;
};
}
现在给定任何二元函数,我们可以创建一个函数对象,在不进行任何递归的情况下折叠它。
与 std::apply
一起完成。
template <typename ... Ts>
auto sumT (std::tuple<Ts...> const & t1) {
return std::apply( fold_over(std::plus<>{}), t1);
}
这是左折。右折叠只涉及更改 fold_over
函数。如果您尝试将空包传递给它,它将无法编译。如果你传递给它一个元素,它会return那个元素。
#include <tuple>
#include <utility>
namespace detail {
template<class F, class T>
struct foldable : std::pair<const F&, T> {
using std::pair<const F&, T>::pair;
template<class V>
constexpr decltype(auto) operator&&(foldable<F, V>&& x) {
return detail::foldable {
this->first,
this->first(this->second, x.second),
};
}
};
template<class F, class T> foldable(const F&, T&&) -> foldable<F, T>;
}
// Folds left a parameter pack
template<class F, class... Args>
constexpr decltype(auto) fold_left_pack(const F& f, Args&&... args) {
static_assert(sizeof...(Args) > 0, "Cannot fold an empty pack.");
return (... && detail::foldable { f, std::forward<Args>(args) }).second;
}
// Folds right a parameter pack
template<class F, class... Args>
constexpr decltype(auto) fold_right_pack(const F& f, Args&&... args) {
static_assert(sizeof...(Args) > 0, "Cannot fold an empty pack.");
return (detail::foldable { f, std::forward<Args>(args) } && ...).second;
}
// Folds left a tuple
template<class F, class Tuple>
constexpr decltype(auto) fold_left(const F& f, Tuple&& tuple) {
return std::apply(
[&](auto&&... args) -> decltype(auto) {
return fold_left_pack(f, std::forward<decltype(args)>(args)...);
},
std::forward<Tuple>(tuple));
}
// Folds right a tuple
template<class F, class Tuple>
constexpr decltype(auto) fold_right(const F& f, Tuple&& tuple) {
return std::apply(
[&](auto&&... args) -> decltype(auto) {
return fold_right_pack(f, std::forward<decltype(args)>(args)...);
},
std::forward<Tuple>(tuple));
}
一个测试:
constexpr auto divide = [](auto x, auto y) { return x / y; };
constexpr auto tuple = std::make_tuple(8, 4, 2);
static_assert(1 == fold_left(divide, tuple));
static_assert(4 == fold_right(divide, tuple));