改进折叠功能
Improving fold function
我已经在 C++ 中实现了一个简单的折叠函数,它接受一个 lambda,并且可以在编译时同时折叠多个向量。我想知道是否可以通过某种方式简化它(我提供了递归版本和迭代递归版本 - 我不确定哪个应该有更好的性能):https://godbolt.org/z/39pW81
也欢迎性能优化 - 在这方面,这两种方法中的任何一种都更快吗?
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperR(Function&& func, const type_identity& id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I>0)
{
return func(foldHelperR<I-1>(std::forward<Function>(func), id, head, tail...), head[I], tail[I]...);
}
else
{
return func(id, head[0], tail[0]...);
}
}
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperI(Function&& func, const type_identity id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I<N-1)
{
return foldHelperI<I+1>(std::forward<Function>(func), func(id, head[I], tail[I]...), head, tail...);
}
else
{
return func(id, head[N-1], tail[N-1]...);
}
}
template<typename type_identity, typename type_head, int N_head, typename ...type_tail, int ...N_tail, typename Function = void (const type_identity&, const type_head&, const type_tail&...)>
constexpr auto fold(Function&& func, const type_identity& id, const tvecn<type_head, N_head>& head, const tvecn<type_tail, N_tail>&... tail)
{
static_assert(std::is_invocable_v<Function, const type_identity&, const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments (possibly wrong argument count).");
static_assert(all_equal_v<N_head, N_tail...>, "Vector sizes must match.");
//return foldHelperR<N_head-1>(std::forward<Function>(func), id, head, tail...);
return foldHelperI<0>(std::forward<Function>(func), id, head, tail...);
}
int main()
{
tvecn<int,3> a(1,2,3);
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
}
用折叠表达式,可能是:
template <typename F, typename Init, std::size_t... Is, typename... Arrays>
constexpr auto fold_impl(F&& f, Init init, std::index_sequence<Is...>, Arrays&&... arrays)
{
auto l = [&](Init init, std::size_t i){ return f(init, arrays[i]...); };
return ((init = l(init, Is)), ...);
}
template <typename F, typename Init, typename Array, typename ... Arrays>
constexpr auto fold(F&& f, Init init, Array&& array, Arrays&&... arrays)
{
static_assert(((arrays.size() == array.size()) && ...));
return fold_impl(f, init, std::make_index_sequence<array.size()>{}, array, arrays...);
}
and can fold multiple vectors at the same time at compile time
不完全是:如果你想运行编译时
(1) 你必须定义 constexpr
的 tvecn
构造函数和
(2) 你必须定义 constexpr
foldhelper 函数和
(3) 你必须声明 constexpr
a
// VVVVVVVVV
constexpr tvecn<int,3> a(1,2,3);
(4) 你必须将 fold 的结果放在一个 constexpr
变量中(或者,更一般地说,在编译时需要该值的地方,作为 C-样式数组,或模板值参数,或 static_assert()
测试)
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, a, a);
I am wondering if it could be simplified in some manner
当然可以。
首先:如果可以的话,避免重新发明轮子:你的 tvecn
是 std::array
的简化版本。
建议:使用std::array
(如果可以的话)
其次:您标记了 C++17,以便可以使用折叠
建议:也用于all_equal
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
一般情况下:当您必须定义必须提供数字的自定义类型特征时,继承(如果可能)自 std::integral_constant
(或 std::bool_constant
,或 std::true_type
,或 std::false_type
:所有 std::integral_constant
专长)。所以你自动继承所有 std::integral_constant
设施。
第三:几乎所有 C++ 标准都使用 std::size_t
,而不是 int
作为大小。
建议:当你必须处理尺寸时,使用std::size_t
,而不是int
。这样可以避免很多烦人的麻烦。
第四:从 main()
你应该 return 只有 EXIT_SUCCESS
(通常为零)或 EXIT_FAILURE
(通常为 1)
建议:避免
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
第五:永远不要低估逗号运算符的力量。
建议:完全避免递归,对辅助函数也使用模板折叠;举个例子
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
您可以从 fold()
中按如下方式调用
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
下面是一个完整的编译和简化的例子
#include <array>
#include <utility>
#include <iostream>
#include <type_traits>
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
template <typename type_identity, typename type_head, std::size_t N_head,
typename ...type_tail, std::size_t ...N_tail,
typename Function = void (type_identity const &,
type_head const &,
type_tail const & ...)>
constexpr auto fold (Function && func, type_identity const & id,
std::array<type_head, N_head> const & head,
std::array<type_tail, N_tail> const & ... tail)
{
static_assert( std::is_invocable_v<Function, const type_identity&,
const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments"
" (possibly wrong argument count).");
static_assert( all_equal_v<N_head, N_tail...>,
"Vector sizes must match.");
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
}
int main()
{
constexpr std::array<int, 3u> b{2, 5, 7};
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, b, b);
std::cout << f << std::endl;
}
我已经在 C++ 中实现了一个简单的折叠函数,它接受一个 lambda,并且可以在编译时同时折叠多个向量。我想知道是否可以通过某种方式简化它(我提供了递归版本和迭代递归版本 - 我不确定哪个应该有更好的性能):https://godbolt.org/z/39pW81
也欢迎性能优化 - 在这方面,这两种方法中的任何一种都更快吗?
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperR(Function&& func, const type_identity& id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I>0)
{
return func(foldHelperR<I-1>(std::forward<Function>(func), id, head, tail...), head[I], tail[I]...);
}
else
{
return func(id, head[0], tail[0]...);
}
}
template<int I, typename type_identity, typename type_head, int N, typename ...type_tail, int ...N_tail, typename Function>
auto foldHelperI(Function&& func, const type_identity id, const tvecn<type_head, N>& head, const tvecn<type_tail, N_tail>&... tail)
{
if constexpr (I<N-1)
{
return foldHelperI<I+1>(std::forward<Function>(func), func(id, head[I], tail[I]...), head, tail...);
}
else
{
return func(id, head[N-1], tail[N-1]...);
}
}
template<typename type_identity, typename type_head, int N_head, typename ...type_tail, int ...N_tail, typename Function = void (const type_identity&, const type_head&, const type_tail&...)>
constexpr auto fold(Function&& func, const type_identity& id, const tvecn<type_head, N_head>& head, const tvecn<type_tail, N_tail>&... tail)
{
static_assert(std::is_invocable_v<Function, const type_identity&, const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments (possibly wrong argument count).");
static_assert(all_equal_v<N_head, N_tail...>, "Vector sizes must match.");
//return foldHelperR<N_head-1>(std::forward<Function>(func), id, head, tail...);
return foldHelperI<0>(std::forward<Function>(func), id, head, tail...);
}
int main()
{
tvecn<int,3> a(1,2,3);
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
}
用折叠表达式,可能是:
template <typename F, typename Init, std::size_t... Is, typename... Arrays>
constexpr auto fold_impl(F&& f, Init init, std::index_sequence<Is...>, Arrays&&... arrays)
{
auto l = [&](Init init, std::size_t i){ return f(init, arrays[i]...); };
return ((init = l(init, Is)), ...);
}
template <typename F, typename Init, typename Array, typename ... Arrays>
constexpr auto fold(F&& f, Init init, Array&& array, Arrays&&... arrays)
{
static_assert(((arrays.size() == array.size()) && ...));
return fold_impl(f, init, std::make_index_sequence<array.size()>{}, array, arrays...);
}
and can fold multiple vectors at the same time at compile time
不完全是:如果你想运行编译时
(1) 你必须定义 constexpr
的 tvecn
构造函数和
(2) 你必须定义 constexpr
foldhelper 函数和
(3) 你必须声明 constexpr
a
// VVVVVVVVV
constexpr tvecn<int,3> a(1,2,3);
(4) 你必须将 fold 的结果放在一个 constexpr
变量中(或者,更一般地说,在编译时需要该值的地方,作为 C-样式数组,或模板值参数,或 static_assert()
测试)
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, a, a);
I am wondering if it could be simplified in some manner
当然可以。
首先:如果可以的话,避免重新发明轮子:你的 tvecn
是 std::array
的简化版本。
建议:使用std::array
(如果可以的话)
其次:您标记了 C++17,以便可以使用折叠
建议:也用于all_equal
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
一般情况下:当您必须定义必须提供数字的自定义类型特征时,继承(如果可能)自 std::integral_constant
(或 std::bool_constant
,或 std::true_type
,或 std::false_type
:所有 std::integral_constant
专长)。所以你自动继承所有 std::integral_constant
设施。
第三:几乎所有 C++ 标准都使用 std::size_t
,而不是 int
作为大小。
建议:当你必须处理尺寸时,使用std::size_t
,而不是int
。这样可以避免很多烦人的麻烦。
第四:从 main()
你应该 return 只有 EXIT_SUCCESS
(通常为零)或 EXIT_FAILURE
(通常为 1)
建议:避免
return fold([](auto x, auto y, auto z) {return x+y+z;}, 0, a, a);
第五:永远不要低估逗号运算符的力量。
建议:完全避免递归,对辅助函数也使用模板折叠;举个例子
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
您可以从 fold()
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
下面是一个完整的编译和简化的例子
#include <array>
#include <utility>
#include <iostream>
#include <type_traits>
template <auto V0, auto ... Vs>
struct all_equal : public std::bool_constant<((V0 == Vs) && ...)>
{ };
template<auto ...N_pack>
constexpr bool all_equal_v = all_equal<N_pack...>::value;
template <std::size_t ... Is, typename F, typename T, typename ... As>
constexpr auto foldHelperF (std::index_sequence<Is...>,
F const & f, T id, As const & ... arrs)
{ return ( ..., (id = [&](auto i){ return f(id, arrs[i]...); }(Is))); }
template <typename type_identity, typename type_head, std::size_t N_head,
typename ...type_tail, std::size_t ...N_tail,
typename Function = void (type_identity const &,
type_head const &,
type_tail const & ...)>
constexpr auto fold (Function && func, type_identity const & id,
std::array<type_head, N_head> const & head,
std::array<type_tail, N_tail> const & ... tail)
{
static_assert( std::is_invocable_v<Function, const type_identity&,
const type_head&, const type_tail &...>,
"The function cannot be invoked with these zip arguments"
" (possibly wrong argument count).");
static_assert( all_equal_v<N_head, N_tail...>,
"Vector sizes must match.");
return foldHelperF(std::make_index_sequence<N_head>{},
std::forward<Function>(func),
id, head, tail...);
}
int main()
{
constexpr std::array<int, 3u> b{2, 5, 7};
constexpr auto f = fold([](auto x, auto y, auto z) {return x+y+z;},
0, b, b);
std::cout << f << std::endl;
}