Search code examples
c++templatesvariadic-templatesvariadic-functions

Сall the add function on every next 2 arguments


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?


Solution

  • 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) ....