I have a function that does some arithmetic on two elements like this:
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;
}
I want to write another template function that will do some logic in the same way, take N arguments and apply the previous function on all these arguments, for example:
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)...);
}
it real? Or maybe there are some alternatives?
What you want can be done by calling std::reduce
on an std::initializer_list
.
Attention: This solution works only if the called function is commutative, i.e. if add(a, b) == add(b, a)
. You may replace std::reduce
by std::accumulate
for the guaranteed order add(add(add(a, b), c), d) ...
. If you really need add(add(a, b), add(c, d)) ...
, this solution will not work!
#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
If you want to make your called add
function part of the interface you can change this to:
#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';
}
If your add
function is simply an operator+
(or another binary operator), you can use a C++17 Fold Expression instead.
#include <iostream>
template <typename ... T>
auto accumulate(const T& ... args) {
return (args + ...); // or (... + args)
}
int main() {
std::cout << accumulate(1) << '\n';
std::cout << accumulate(1, 2) << '\n';
std::cout << accumulate(1, 2, 3) << '\n';
std::cout << accumulate(1, 2, 3, 4) << '\n';
std::cout << accumulate(1, 2, 3, 4, 5) << '\n';
}
Note that (args + ...)
means ... (a + (b + (c + d))
and (... + args)
means (((a + b) + c) + d) ...
.