我有一个函数可以像这样对两个元素进行一些算术运算:
template <typename Type>
Type add(const Type& a, const Type& b)
{
// some logic
if(!((b >= 0) && (a > std::numeric_limits<T>::max() - b))
return a + b;
}
我想写另一个模板函数,它将以相同的方式执行一些逻辑,接受 N 个参数并将前一个函数应用于所有这些参数,例如:
template <typename... Args>
idk_how_type_need add_n_elements(Args... args)
{
return //here i want smth like -> add(add(arg0, arg1), add(arg2, arg3)...);
}
是真的吗?或者也许还有其他选择?
你想要的可以通过在
std::reduce
上调用std::initializer_list
来完成。
注意: 此解决方案仅在被调用函数可交换时才有效,即如果
add(a, b) == add(b, a)
.
#include <concepts> // for optional C++20 concept line
#include <iostream>
#include <numeric>
template <typename T>
T add(const T& a, const T& b) {
return a + b;
}
template <typename T, typename ... U>
requires (std::same_as<T, U> && ...) // optional C++20 concept line
T reduce(const T& first, const U& ... args) {
auto const list = std::initializer_list<T>{args ...};
return std::reduce(list.begin(), list.end(), first, add<T>);
}
int main() {
std::cout << reduce(1) << '\n';
std::cout << reduce(1, 2) << '\n';
std::cout << reduce(1, 2, 3) << '\n';
std::cout << reduce(1, 2, 3, 4) << '\n';
std::cout << reduce(1, 2, 3, 4, 5) << '\n';
}
1
3
6
10
15
如果你想让调用的
add
函数成为界面的一部分,你可以将其更改为:
#include <concepts> // for optional C++20 concept line
#include <iostream>
#include <numeric>
template <typename T>
T add(const T& a, const T& b) {
return a + b;
}
template <typename Fn, typename T, typename ... U>
requires (std::same_as<T, U> && ...) && std::invocable<Fn, T, T> // optional C++20 concept line
T reduce(Fn&& fn, const T& first, const U& ... args) {
auto const list = std::initializer_list<T>{args ...};
return std::reduce(list.begin(), list.end(), first, std::forward<Fn>(fn));
}
int main() {
std::cout << reduce(add<int>, 1) << '\n';
std::cout << reduce(add<int>, 1, 2) << '\n';
std::cout << reduce(add<int>, 1, 2, 3) << '\n';
std::cout << reduce(add<int>, 1, 2, 3, 4) << '\n';
std::cout << reduce(add<int>, 1, 2, 3, 4, 5) << '\n';
}
嗯,用户Benjamin Buch的回答好像是
关于1.:对于只需添加少量值,我们不需要
std::reduce
。因此,首先将值放入一个容器中,这里是一个std::initializer_list
,然后减少这个容器,对于添加值来说太复杂了。 std::reduce
有很大的优势,如果你想使用关联和交换运算符并行化或矢量化大数据。惯用的首选解决方案是使用 fold 表达式。我将在下面解释。
关于 2.:这根本没有解决。我将在下面展示一个解决方案,也是基于折叠表达式。
用折叠表达式求和。如果您查看 CPP 参考资料here,您会看到如何轻松减少参数包(在您的示例中
Args... args
)。如果你看那里,那么你可以阅读:
Syntax:
( pack op ... ) (1)
( ... op pack ) (2)
( pack op ... op init ) (3)
( init op ... op pack ) (4)
1) unary right fold
2) unary left fold
3) binary right fold
4) binary left fold
如果您将用例“pack”替换为“args”,将“op”替换为“+”,那么它会这样读:
( args + ... )
( ... + args )
( args + ... + sum )
( sum + ... + args )
所有变体都有用例。但是让我首先向您展示基于一元右折叠的最简单的解决方案:
#include <iostream>
template <typename...Args> // Unary right fold
auto sum1(Args ... args) {
return (args + ...);
}
int main() {
std::cout << sum1(1, 2, 3, 4) << '\n';
}
这真的非常紧凑简单。
您可以在 CPP 参考中阅读,这里会发生什么。
解释 折叠表达式的实例化扩展表达式 e 如下:
- 一元右折叠 (E op ...) 变为 (E1 op (... op (EN-1 op EN)))
对于我们上面的折叠表达式
(args + ...)
,我们将得到以下内容:
(1 + ( 2 + (3 + 4)))
。对于 '+' 运算符,我们可以省略所有大括号,最后得到:1 + 2 + 3 + 4
.
不错
请看下面的一段代码,我们使用了折叠表达式的所有 4 种变体:
#include <iostream>
#include <tuple>
template <typename...Args> // Unary right fold
auto sum1(Args ... args) {
return (args + ...);
}
template <typename...Args> // Unary left fold
auto sum2(Args ... args) {
return (... + args);
}
template <typename...Args> // Binary right fold
auto sum3(Args ... args) {
std::tuple_element_t<0, std::tuple<Args...>> sum{};
sum = (args + ... + sum);
return sum;
}
template <typename...Args> // Binary left fold
auto sum4(Args ... args) {
std::tuple_element_t<0, std::tuple<Args...>> sum{};
sum = (sum + ... + args);
return sum;
}
int main() {
std::cout << sum1(1, 2, 3, 4) << '\n';
std::cout << sum2(1, 2, 3, 4) << '\n';
std::cout << sum3(1, 2, 3, 4) << '\n';
std::cout << sum4(1, 2, 3, 4) << '\n';
}
.
下一步。到第二部分。如何在参数包的每个参数上应用函数。还有很多潜在的解决方案,但是,因为我在解释折叠表达式,所以我将展示一个针对小函数的解决方案,所以,特别是 lambda,通过使用折叠表达式。
此处解决方案的主要部分是将“逗号”运算符与一元右折叠结合使用。
由于不经常使用逗号运算符,让我们再次阅读它here.
所以,
lhs, rhs
做了以下事情:
首先,评估左操作数 lhs 并丢弃其结果值。 然后,发生一个序列点,这样 lhs 的所有副作用就完成了。 然后,计算右操作数 rhs,其结果由逗号运算符作为非左值返回。
这可以与一元右折完美结合。
让我们采用最简单形式的lambda:
[]{}
。你只看到捕获和身体。这是一个完全有效的 Lambda。如果你想调用这个lambda,那么你可以简单地写[]{} ()
。这会调用一个空的 lambda 并且什么都不做。仅用于演示目的:如果我们使用求和示例:
template <typename...Args>
auto sum5(Args ... args) {
std::tuple_element_t<0, std::tuple<Args...>> sum{};
// comma lhs: Lambda comma rhs
([&]{sum += args;} () , ...);
return sum;
}
然后会发生以下情况:
sum
()
调用函数。这是逗号运算符的“lhs”如果你一步步分析这个,你就会明白了。
下一步就简单了。你可以在你的 lambda 中加入更多的功能。这样,您就可以对参数包的所有参数应用一个函数。
这将是您预期功能的示例:
#include <iostream>
#include <tuple>
#include <utility>
#include <limits>
#include <iomanip>
template <typename...Args>
using MyType = std::tuple_element_t<0, std::tuple<Args...>>;
template <typename...Args>
std::pair<bool, MyType<Args...>> sumsWithOverflowCheck(Args ... args) {
bool overflow{};
MyType <Args...>sum{};
([&] // Lambda Capture
{ // Lambda Body
const MyType<Args...> maxDelta{ std::numeric_limits<MyType<Args...>>::max() - sum };
if (args > maxDelta) overflow = true;
sum += args;
} // End of lambda body
() // Call the Lampda
, ...); // unary right fold over comma operator
return { overflow, sum };
}
int main() {
{ // Test 1
const auto& [overflow, sum] = sumsWithOverflowCheck(1, 2, 3, 4);
std::cout << "\nSum: " << std::setw(12) << sum << "\tOverflow: " << std::boolalpha << overflow << '\n';
}
{ //Test 2
const auto& [overflow, sum] = sumsWithOverflowCheck(1,2,(std::numeric_limits<int>::max()-10), 20, 30);
std::cout << "Sum: " << std::setw(12) << sum << "\tOverflow: " << overflow << '\n';
}
}
当然,你会消除 lambda 中所有不必要的换行符,以使其更紧凑。
如前所述,有很多方法可以做到。可能最简单的解决方案是添加重载函数来处理 3 个或更多参数并递归调用自身,如下所示:
#include <iostream>
#include <numeric>
//original function
template <typename Type>
Type add(const Type& a, const Type& b)
{
// some logic
if(!((b >= 0) && (a > std::numeric_limits<Type>::max() - b)))
return a + b;
throw std::runtime_error("some error");
}
//overloaded function
template <typename Type, typename ...Type2>
Type add(const Type& a, const Type& b, const Type2 & ...other)
{
return add(add(a,b), other...); //calls itself if num of params > 2, or original "add" otherwise
}
int main() {
std::cout << add(1, 2) << '\n';
std::cout << add(1, 2, 3) << '\n';
std::cout << add(1, 2, 3, 4) << '\n';
std::cout << add(1, 2, 3, 4, 5) << '\n';
return 0;
}
所以,我只添加了一个单行函数,它很容易理解和解释它是如何工作的,通常你可以通过将可变参数模板与递归结合起来使很多复杂的东西变得容易得多