2
\$\begingroup\$

This is a follow-up question for A recursive_sum Template Function Implementation with Unwrap Level in C++ and A recursive_unwrap_type_t Struct Implementation in C++. Considering the answer provided by G. Sliepen:

Make it more generic

Summing is a very specific operation. What if you want to calculate the product of all elements instead? Or get the minimum or maximum value? Instead of hardcoding the operation, create a recursive_reduce() that works like std::reduce(). You can still have it sum by default if you don't specify which operation to perform.

I am trying to implement recursive_reduce_all() template function in C++ which performs operation on input container exhaustively. The test cases includes:

  • Pure recursive_reduce_all function test with nested std::vectors

  • recursive_reduce_all function test with execution policies

  • recursive_reduce_all function test with initial value

  • recursive_reduce_all function test with execution policies and initial value

  • recursive_reduce_all function test with initial value and specified operation

  • recursive_reduce_all function test with execution policies, initial value and specified operation (in generic lambda)

The experimental implementation

  • recursive_reduce_all() template function implementation

    /*  recursive_reduce_all template function performs operation on input container exhaustively
    */
    template<arithmetic T>
    constexpr auto recursive_reduce_all(const T& input)
    {
        return input;
    }
    
    template<std::ranges::input_range T>
    requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              recursive_depth<T>() == 1)
    constexpr auto recursive_reduce_all(const T& input)
    {
        return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input));
    }
    
    //  overload for std::array
    template<template<class, std::size_t> class Container,
                  typename T,
                  std::size_t N>
    requires (arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
              recursive_depth<Container<T, N>>() == 1)
    constexpr auto recursive_reduce_all(const Container<T, N>& input)
    {
        return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input));
    }
    
    template<std::ranges::input_range T>
    requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::ranges::input_range<recursive_unwrap_type_t<1, T>>)
    constexpr auto recursive_reduce_all(const T& input)
    {
        auto result = recursive_reduce_all(
            UL::recursive_transform<recursive_depth<T>() - 1>(input, [](auto&& element){ return recursive_reduce_all(element); })
            );
        return result;
    }
    
    //  recursive_reduce_all template function with execution policy
    template<class ExPo, arithmetic T>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
    {
        return input;
    }
    
    template<class ExPo, std::ranges::input_range T>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              recursive_depth<T>() == 1)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
    {
        return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input));
    }
    
    //  recursive_reduce_all template function with execution policy, overload for std::array
    template<class ExPo, template<class, std::size_t> class Container,
                  typename T,
                  std::size_t N>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
              recursive_depth<Container<T, N>>() == 1)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input)
    {
        return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input));
    }
    
    template<class ExPo, std::ranges::input_range T>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::ranges::input_range<recursive_unwrap_type_t<1, T>>)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
    {
        auto result = recursive_reduce_all(
            UL::recursive_transform<recursive_depth<T>() - 1>(
                execution_policy,
                input,
                [&](auto&& element){ return recursive_reduce_all(execution_policy, element); }
                )
            );
        return result;
    }
    
    //  recursive_reduce_all template function with initial value
    template<arithmetic T>
    constexpr auto recursive_reduce_all(const T& input1, const T& input2)
    {
        return input1 + input2;
    }
    
    template<std::ranges::input_range T, class TI>
    requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
              recursive_depth<T>() == 1)
    constexpr auto recursive_reduce_all(const T& input, TI init)
    {
        return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init);
    }
    
    //  recursive_reduce_all template function with initial value, overload for std::array
    template<template<class, std::size_t> class Container,
                  typename T,
                  std::size_t N,
                  class TI>
    requires (arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
              std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
              recursive_depth<Container<T, N>>() == 1)
    constexpr auto recursive_reduce_all(const Container<T, N>& input, TI init)
    {
        return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init);
    }
    
    template<std::ranges::input_range T, class TI>
    requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI>)
    constexpr auto recursive_reduce_all(const T& input, TI init)
    {
        auto result = init + recursive_reduce_all(
            UL::recursive_transform<recursive_depth<T>() - 1>(
                input,
                [&](auto&& element){ return recursive_reduce_all(element); })
            );
        return result;
    }
    
    //  recursive_reduce_all template function with execution policy and initial value
    template<class ExPo, arithmetic T>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input1, const T& input2)
    {
        return input1 + input2;
    }
    
    template<class ExPo, std::ranges::input_range T, class TI>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
              recursive_depth<T>() == 1)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init)
    {
        return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init);
    }
    
    //  recursive_reduce_all template function with execution policy and initial value, overload for std::array
    template<class ExPo,
                  template<class, std::size_t> class Container,
                  typename T,
                  std::size_t N,
                  class TI>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
              std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
              recursive_depth<Container<T, N>>() == 1)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input, TI init)
    {
        return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init);
    }
    
    template<class ExPo, std::ranges::input_range T, class TI>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI>)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init)
    {
        auto result = init + recursive_reduce_all(
            UL::recursive_transform<recursive_depth<T>() - 1>(
                execution_policy,
                input,
                [&](auto&& element){ return recursive_reduce_all(execution_policy, element); })
            );
        return result;
    }
    
    //  recursive_reduce_all template function with initial value and specified operation
    template<arithmetic T, class BinaryOp>
    requires (std::regular_invocable<BinaryOp, T, T>)
    constexpr auto recursive_reduce_all(const T& input1, const T& input2, BinaryOp binary_op)
    {
        return std::invoke(binary_op, input1, input2);
    }
    
    template<std::ranges::input_range T, class TI, class BinaryOp>
    requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
              recursive_depth<T>() == 1 &&
              std::regular_invocable<
                BinaryOp,
                recursive_unwrap_type_t<recursive_depth<T>(),T>,
                recursive_unwrap_type_t<recursive_depth<T>(), T>>
              )
    constexpr auto recursive_reduce_all(const T& input, TI init, BinaryOp binary_op)
    {
        return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
    }
    
    //  recursive_reduce_all template function with initial value and specified operation, overload for std::array
    template<template<class, std::size_t> class Container,
                  typename T,
                  std::size_t N,
                  class TI,
                  class BinaryOp>
    requires (arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
              std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
              recursive_depth<Container<T, N>>() == 1 &&
              std::regular_invocable<
                BinaryOp,
                recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>,
                recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>>
              )
    constexpr auto recursive_reduce_all(const Container<T, N>& input, TI init, BinaryOp binary_op)
    {
        return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
    }
    
    template<std::ranges::input_range T, class TI, class BinaryOp>
    requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
              std::regular_invocable<
                BinaryOp,
                recursive_unwrap_type_t<recursive_depth<T>(),T>,
                recursive_unwrap_type_t<recursive_depth<T>(), T>>
              )
    constexpr auto recursive_reduce_all(const T& input, TI init, BinaryOp binary_op)
    {
        auto result = init + recursive_reduce_all(
            UL::recursive_transform<recursive_depth<T>() - 1>(
                input,
                [&](auto&& element){ return recursive_reduce_all(element, init, binary_op); })
            );
        return result;
    }
    
    //  recursive_reduce_all template function with execution policy, initial value and specified operation
    template<class ExPo, arithmetic T, class BinaryOp>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              std::regular_invocable<BinaryOp, T, T>)
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input1, const T& input2, BinaryOp binary_op)
    {
        return std::invoke(binary_op, input1, input2);
    }
    
    template<class ExPo, std::ranges::input_range T, class TI, class BinaryOp>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
              recursive_depth<T>() == 1 &&
              std::regular_invocable<
                BinaryOp,
                recursive_unwrap_type_t<recursive_depth<T>(),T>,
                recursive_unwrap_type_t<recursive_depth<T>(), T>>
              )
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init, BinaryOp binary_op)
    {
        return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
    }
    
    //  recursive_reduce_all template function with execution policy, initial value and specified operation, overload for std::array
    template<class ExPo,
                  template<class, std::size_t> class Container,
                  typename T,
                  std::size_t N,
                  class TI, class BinaryOp>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
              std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
              recursive_depth<Container<T, N>>() == 1 &&
              std::regular_invocable<
                BinaryOp,
                recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>,
                recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>>
              )
    constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input, TI init, BinaryOp binary_op)
    {
        return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
    }
    
    template<class ExPo, std::ranges::input_range T, class TI, class BinaryOp>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
              std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
              std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
              std::regular_invocable<
                BinaryOp,
                recursive_unwrap_type_t<recursive_depth<T>(),T>,
                recursive_unwrap_type_t<recursive_depth<T>(), T>>
              )
    constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init, BinaryOp binary_op)
    {
        auto result = init + recursive_reduce_all(
            UL::recursive_transform<recursive_depth<T>() - 1>(
                execution_policy,
                input,
                [&](auto&& element){ return recursive_reduce_all(execution_policy, element, init, binary_op); })
            );
        return result;
    }
    

Full Testing Code

The full testing code:

//  A `recursive_reduce_all` Template Function Implementation in C++

#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <complex>
#include <concepts>
#include <deque>
#include <execution>
#include <exception>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <mutex>
#include <numeric>
#include <optional>
#include <queue>
#include <ranges>
#include <stack>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

//  is_reservable concept
template<class T>
concept is_reservable = requires(T input)
{
    input.reserve(1);
};

//  is_sized concept, https://codereview.stackexchange.com/a/283581/231235
template<class T>
concept is_sized = requires(T x)
{
    std::size(x);
};

//  Reference: https://stackoverflow.com/a/58067611/6667035
template <typename T>
concept arithmetic = std::is_arithmetic_v<T>;

//  recursive_depth function implementation
template<typename T>
constexpr std::size_t recursive_depth()
{
    return 0;
}

template<std::ranges::input_range Range>
constexpr std::size_t recursive_depth()
{
    return recursive_depth<std::ranges::range_value_t<Range>>() + 1;
}

//  recursive_variadic_invoke_result_t implementation
template<std::size_t, typename, typename, typename...>
struct recursive_variadic_invoke_result { };

template<typename F, class...Ts1, template<class...>class Container1, typename... Ts>
struct recursive_variadic_invoke_result<1, F, Container1<Ts1...>, Ts...>
{
    using type = Container1<std::invoke_result_t<F,
        std::ranges::range_value_t<Container1<Ts1...>>,
        std::ranges::range_value_t<Ts>...>>;
};

template<std::size_t unwrap_level, typename F, class...Ts1, template<class...>class Container1, typename... Ts>
requires (  std::ranges::input_range<Container1<Ts1...>> &&
            requires { typename recursive_variadic_invoke_result<
                                    unwrap_level - 1,
                                    F,
                                    std::ranges::range_value_t<Container1<Ts1...>>,
                                    std::ranges::range_value_t<Ts>...>::type; })                //  The rest arguments are ranges
struct recursive_variadic_invoke_result<unwrap_level, F, Container1<Ts1...>, Ts...>
{
    using type = Container1<
        typename recursive_variadic_invoke_result<
        unwrap_level - 1,
        F,
        std::ranges::range_value_t<Container1<Ts1...>>,
        std::ranges::range_value_t<Ts>...
        >::type>;
};

template<std::size_t unwrap_level, typename F, typename T1, typename... Ts>
using recursive_variadic_invoke_result_t = typename recursive_variadic_invoke_result<unwrap_level, F, T1, Ts...>::type;

//  recursive_unwrap_type_t struct implementation
template<std::size_t, typename, typename...>
struct recursive_unwrap_type { };

template<class...Ts1, template<class...>class Container1, typename... Ts>
struct recursive_unwrap_type<1, Container1<Ts1...>, Ts...>
{
    using type = std::ranges::range_value_t<Container1<Ts1...>>;
};

template<std::size_t unwrap_level, class...Ts1, template<class...>class Container1, typename... Ts>
requires (  std::ranges::input_range<Container1<Ts1...>> &&
            requires { typename recursive_unwrap_type<
                                    unwrap_level - 1,
                                    std::ranges::range_value_t<Container1<Ts1...>>,
                                    std::ranges::range_value_t<Ts>...>::type; })                //  The rest arguments are ranges
struct recursive_unwrap_type<unwrap_level, Container1<Ts1...>, Ts...>
{
    using type = typename recursive_unwrap_type<
        unwrap_level - 1,
        std::ranges::range_value_t<Container1<Ts1...>>
        >::type;
};

template<std::size_t unwrap_level, typename T1, typename... Ts>
using recursive_unwrap_type_t = typename recursive_unwrap_type<unwrap_level, T1, Ts...>::type;

//  recursive_array_unwrap_type struct implementation, https://stackoverflow.com/a/76347485/6667035
template<std::size_t, typename>
struct recursive_array_unwrap_type { };

template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
struct recursive_array_unwrap_type<1, Container<T, N>>
{
    using type = std::ranges::range_value_t<Container<T, N>>;
};

template<std::size_t unwrap_level, template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
requires (  std::ranges::input_range<Container<T, N>> &&
            requires { typename recursive_array_unwrap_type<
                                    unwrap_level - 1,
                                    std::ranges::range_value_t<Container<T, N>>>::type; })                //  The rest arguments are ranges
struct recursive_array_unwrap_type<unwrap_level, Container<T, N>>
{
    using type = typename recursive_array_unwrap_type<
        unwrap_level - 1,
        std::ranges::range_value_t<Container<T, N>>
        >::type;
};

template<std::size_t unwrap_level, class Container>
using recursive_array_unwrap_type_t = typename recursive_array_unwrap_type<unwrap_level, Container>::type;

//  https://codereview.stackexchange.com/a/253039/231235
template<template<class...> class Container = std::vector, std::size_t dim, class T>
constexpr auto n_dim_container_generator(T input, std::size_t times)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        return Container(times, n_dim_container_generator<Container, dim - 1, T>(input, times));
    }
}

namespace UL                                                //   unwrap_level
{
    template< std::ranges::input_range Container,
              std::copy_constructible F>
    requires (std::ranges::view<Container>&&
              std::is_object_v<F>)
    constexpr auto make_view(const Container& input, const F& f) noexcept
    {
        return std::ranges::transform_view(
                input,
                [&f](const auto&& element) constexpr { return recursive_transform(element, f ); } );
    }

    /* Override make_view to catch dangling references.  A borrowed range is
    * safe from dangling..
    */
    template <std::ranges::input_range T>
    requires (!std::ranges::borrowed_range<T>)
    constexpr std::ranges::dangling make_view(T&&) noexcept
    {
        return std::ranges::dangling();
    }

    //  clone_empty_container template function implementation
    template< std::size_t unwrap_level = 1,
              std::ranges::input_range Container,
              std::copy_constructible F>
    requires (std::ranges::view<Container>&&
              std::is_object_v<F>)
    constexpr auto clone_empty_container(const Container& input, const F& f) noexcept
    {
        const auto view = make_view(input, f);
        recursive_variadic_invoke_result<unwrap_level, F, Container> output(std::span{input});
        return output;
    }
    
    //  recursive_transform template function implementation (the version with unwrap_level template parameter)
    template<   std::size_t unwrap_level = 1,
                class T,
                std::copy_constructible F>
    requires (unwrap_level <= recursive_depth<T>() &&        //  handling incorrect unwrap levels more gracefully, https://codereview.stackexchange.com/a/283563/231235
              std::ranges::view<T>&&
              std::is_object_v<F>)         
    constexpr auto recursive_transform(const T& input, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            auto output = clone_empty_container(input, f);
            if constexpr (is_reservable<decltype(output)>&&
                          is_sized<decltype(input)>)
            {
                output.reserve(input.size());
                std::ranges::transform(
                    input,
                    std::ranges::begin(output),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
                );
            }
            else
            {
                std::ranges::transform(
                    input,
                    std::inserter(output, std::ranges::end(output)),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
                );
            }
            return output;
        }
        else if constexpr(std::regular_invocable<F, T>)
        {
            return std::invoke(f, input);
        }
        else
        {
            static_assert(!std::regular_invocable<F, T>, "Uninvocable?");
        }
    }

    /* This overload of recursive_transform is to support std::array
    */
    template< std::size_t unwrap_level = 1,
              template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              typename F >
    requires (std::ranges::input_range<Container<T, N>>)
    constexpr auto recursive_transform(const Container<T, N>& input, const F& f)
    {
        Container<recursive_variadic_invoke_result_t<unwrap_level, F, T>, N> output;

        std::ranges::transform(
                        input,
                        std::ranges::begin(output),
                        [&f](auto&& element){ return recursive_transform<unwrap_level - 1>(element, f); }
                    );

        return output;
    }

    //  recursive_transform function implementation (the version with unwrap_level, without using view)
    template<std::size_t unwrap_level = 1, class T, class F>
    requires (!std::ranges::view<T>)
    constexpr auto recursive_transform(const T& input, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            static_assert(unwrap_level <= recursive_depth<T>(),
                "unwrap level higher than recursion depth of input");   //  trying to handle incorrect unwrap levels more gracefully
            recursive_variadic_invoke_result_t<unwrap_level, F, T> output{};
            std::ranges::transform(
                input,                      //  passing a range to std::ranges::transform()
                std::inserter(output, std::ranges::end(output)),
                [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
            );
            return output;
        }
        else
        {
            return std::invoke(f, input);   //   use std::invoke()
        }
    }

    //  recursive_transform implementation (the version with unwrap_level, with execution policy)
    template<std::size_t unwrap_level = 1, class ExPo, class T, class F>
    requires (unwrap_level <= recursive_depth<T>() &&        //  handling incorrect unwrap levels more gracefully, https://codereview.stackexchange.com/a/283563/231235
              std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
    constexpr auto recursive_transform(ExPo execution_policy, const T& input, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            recursive_variadic_invoke_result_t<unwrap_level, F, T> output{};
            output.resize(input.size());
            std::mutex mutex;
            std::transform(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
                [&](auto&& element)
                {
                    std::lock_guard lock(mutex);
                    return recursive_transform<unwrap_level - 1>(execution_policy, element, f);
                });
            return output;
        }
        else
        {
            return f(input);
        }
    }
}

/*  recursive_reduce_all template function performs operation on input container exhaustively
*/
template<arithmetic T>
constexpr auto recursive_reduce_all(const T& input)
{
    return input;
}

template<std::ranges::input_range T>
requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(const T& input)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input));
}

//  overload for std::array
template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
requires (arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(const Container<T, N>& input)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input));
}

template<std::ranges::input_range T>
requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>>)
constexpr auto recursive_reduce_all(const T& input)
{
    auto result = recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(input, [](auto&& element){ return recursive_reduce_all(element); })
        );
    return result;
}

//  recursive_reduce_all template function with execution policy
template<class ExPo, arithmetic T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
{
    return input;
}

template<class ExPo, std::ranges::input_range T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input));
}

//  recursive_reduce_all template function with execution policy, overload for std::array
template<class ExPo, template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input));
}

template<class ExPo, std::ranges::input_range T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input)
{
    auto result = recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            execution_policy,
            input,
            [&](auto&& element){ return recursive_reduce_all(execution_policy, element); }
            )
        );
    return result;
}

//  recursive_reduce_all template function with initial value
template<arithmetic T>
constexpr auto recursive_reduce_all(const T& input1, const T& input2)
{
    return input1 + input2;
}

template<std::ranges::input_range T, class TI>
requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(const T& input, TI init)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init);
}

//  recursive_reduce_all template function with initial value, overload for std::array
template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI>
requires (arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(const Container<T, N>& input, TI init)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init);
}

template<std::ranges::input_range T, class TI>
requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI>)
constexpr auto recursive_reduce_all(const T& input, TI init)
{
    auto result = init + recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            input,
            [&](auto&& element){ return recursive_reduce_all(element); })
        );
    return result;
}

//  recursive_reduce_all template function with execution policy and initial value
template<class ExPo, arithmetic T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input1, const T& input2)
{
    return input1 + input2;
}

template<class ExPo, std::ranges::input_range T, class TI>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init);
}

//  recursive_reduce_all template function with execution policy and initial value, overload for std::array
template<class ExPo,
              template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1)
constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input, TI init)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init);
}

template<class ExPo, std::ranges::input_range T, class TI>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init)
{
    auto result = init + recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            execution_policy,
            input,
            [&](auto&& element){ return recursive_reduce_all(execution_policy, element); })
        );
    return result;
}

//  recursive_reduce_all template function with initial value and specified operation
template<arithmetic T, class BinaryOp>
requires (std::regular_invocable<BinaryOp, T, T>)
constexpr auto recursive_reduce_all(const T& input1, const T& input2, BinaryOp binary_op)
{
    return std::invoke(binary_op, input1, input2);
}

template<std::ranges::input_range T, class TI, class BinaryOp>
requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(const T& input, TI init, BinaryOp binary_op)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

//  recursive_reduce_all template function with initial value and specified operation, overload for std::array
template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI,
              class BinaryOp>
requires (arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>>
          )
constexpr auto recursive_reduce_all(const Container<T, N>& input, TI init, BinaryOp binary_op)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

template<std::ranges::input_range T, class TI, class BinaryOp>
requires (arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(const T& input, TI init, BinaryOp binary_op)
{
    auto result = init + recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            input,
            [&](auto&& element){ return recursive_reduce_all(element, init, binary_op); })
        );
    return result;
}

//  recursive_reduce_all template function with execution policy, initial value and specified operation
template<class ExPo, arithmetic T, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          std::regular_invocable<BinaryOp, T, T>)
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input1, const T& input2, BinaryOp binary_op)
{
    return std::invoke(binary_op, input1, input2);
}

template<class ExPo, std::ranges::input_range T, class TI, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          recursive_depth<T>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init, BinaryOp binary_op)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

//  recursive_reduce_all template function with execution policy, initial value and specified operation, overload for std::array
template<class ExPo,
              template<class, std::size_t> class Container,
              typename T,
              std::size_t N,
              class TI, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>> &&
          std::same_as<recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>, TI> &&
          recursive_depth<Container<T, N>>() == 1 &&
          std::regular_invocable<
            BinaryOp,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>,
            recursive_array_unwrap_type_t<recursive_depth<Container<T, N>>(), Container<T, N>>>
          )
constexpr auto recursive_reduce_all(ExPo execution_policy, const Container<T, N>& input, TI init, BinaryOp binary_op)
{
    return std::reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), init, binary_op);
}

template<class ExPo, std::ranges::input_range T, class TI, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          arithmetic<recursive_unwrap_type_t<recursive_depth<T>(), T>> &&
          std::ranges::input_range<recursive_unwrap_type_t<1, T>> &&
          std::same_as<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI> &&
          std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>(),T>,
            recursive_unwrap_type_t<recursive_depth<T>(), T>>
          )
constexpr auto recursive_reduce_all(ExPo execution_policy, const T& input, TI init, BinaryOp binary_op)
{
    auto result = init + recursive_reduce_all(
        UL::recursive_transform<recursive_depth<T>() - 1>(
            execution_policy,
            input,
            [&](auto&& element){ return recursive_reduce_all(execution_policy, element, init, binary_op); })
        );
    return result;
}

template<class T>
requires (std::ranges::input_range<T>)
constexpr auto recursive_print(const T& input, const int level = 0)
{
    T output = input;
    std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
    std::transform(input.cbegin(), input.cend(), output.begin(), 
        [level](auto&& x)
        {
            std::cout << std::string(level, ' ') << x << std::endl;
            return x;
        }
    );
    return output;
}

template<class T>
requires (std::ranges::input_range<T> &&
          std::ranges::input_range<std::ranges::range_value_t<T>>)
constexpr T recursive_print(const T& input, const int level = 0)
{
    T output = input;
    std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
    std::transform(input.cbegin(), input.cend(), output.begin(),
        [level](auto&& element)
        {
            return recursive_print(element, level + 1);
        }
    );
    return output;
}

void recursive_reduce_all_tests_vector()
{
    auto test_vectors = n_dim_container_generator<std::vector, 4, double>(1, 4);
    
    std::cout << "Play with test_vectors:\n\n";

    std::cout << "Pure recursive_reduce_all function test: \n";
    auto recursive_reduce_all_result1 = recursive_reduce_all(test_vectors);
    std::cout << recursive_reduce_all_result1 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq): \n";
    auto recursive_reduce_all_result2 = recursive_reduce_all(std::execution::seq, test_vectors);
    std::cout << recursive_reduce_all_result2 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par): \n";
    auto recursive_reduce_all_result3 = recursive_reduce_all(std::execution::par, test_vectors);
    std::cout << recursive_reduce_all_result3 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq): \n";
    auto recursive_reduce_all_result4 = recursive_reduce_all(std::execution::par_unseq, test_vectors);
    std::cout << recursive_reduce_all_result4 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with initial value: \n";
    auto recursive_reduce_all_result5 = recursive_reduce_all(test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result5 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: \n";
    auto recursive_reduce_all_result6 = recursive_reduce_all(std::execution::seq, test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result6 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par) and initial value: \n";
    auto recursive_reduce_all_result7 = recursive_reduce_all(std::execution::par, test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result7 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: \n";
    auto recursive_reduce_all_result8 = recursive_reduce_all(std::execution::par_unseq, test_vectors, static_cast<double>(1));
    std::cout << recursive_reduce_all_result8 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with initial value and specified operation: \n";
    auto recursive_reduce_all_result9 = recursive_reduce_all(test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result9 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: \n";
    auto recursive_reduce_all_result10 = recursive_reduce_all(std::execution::seq, test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result10 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: \n";
    auto recursive_reduce_all_result11 = recursive_reduce_all(std::execution::par, test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result11 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: \n";
    auto recursive_reduce_all_result12 = recursive_reduce_all(std::execution::par_unseq, test_vectors, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result12 << "\n\n";
    
    return;
}

void recursive_reduce_all_tests_array()
{
    auto test_array = std::array<double, 4>{1, 1, 1, 1};

    std::cout << "Play with test_array:\n\n";

    std::cout << "Pure recursive_reduce_all function test: \n";
    auto recursive_reduce_all_result1 = recursive_reduce_all(test_array);
    std::cout << recursive_reduce_all_result1 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq): \n";
    auto recursive_reduce_all_result2 = recursive_reduce_all(std::execution::seq, test_array);
    std::cout << recursive_reduce_all_result2 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par): \n";
    auto recursive_reduce_all_result3 = recursive_reduce_all(std::execution::par, test_array);
    std::cout << recursive_reduce_all_result3 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq): \n";
    auto recursive_reduce_all_result4 = recursive_reduce_all(std::execution::par_unseq, test_array);
    std::cout << recursive_reduce_all_result4 << "\n\n";
     
    std::cout << "recursive_reduce_all function test with initial value: \n";
    auto recursive_reduce_all_result5 = recursive_reduce_all(test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result5 << "\n\n";
    
    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: \n";
    auto recursive_reduce_all_result6 = recursive_reduce_all(std::execution::seq, test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result6 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par) and initial value: \n";
    auto recursive_reduce_all_result7 = recursive_reduce_all(std::execution::par, test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result7 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: \n";
    auto recursive_reduce_all_result8 = recursive_reduce_all(std::execution::par_unseq, test_array, static_cast<double>(1));
    std::cout << recursive_reduce_all_result8 << "\n\n";

    std::cout << "recursive_reduce_all function test with initial value and specified operation: \n";
    auto recursive_reduce_all_result9 = recursive_reduce_all(test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result9 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: \n";
    auto recursive_reduce_all_result10 = recursive_reduce_all(std::execution::seq, test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result10 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: \n";
    auto recursive_reduce_all_result11 = recursive_reduce_all(std::execution::par, test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result11 << "\n\n";

    std::cout << "recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: \n";
    auto recursive_reduce_all_result12 = recursive_reduce_all(std::execution::par_unseq, test_array, static_cast<double>(1), [](auto&& input1, auto&& input2) { return input1 * input2; });
    std::cout << recursive_reduce_all_result12 << "\n\n";

    return;
}

int main()
{
    auto start = std::chrono::system_clock::now();
    recursive_reduce_all_tests_vector();
    recursive_reduce_all_tests_array();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
    return 0;
}

The output of the test code above:

Play with test_vectors:

Pure recursive_reduce_all function test: 
256

recursive_reduce_all function test with execution policy (std::execution::seq): 
256

recursive_reduce_all function test with execution policy (std::execution::par): 
256

recursive_reduce_all function test with execution policy (std::execution::par_unseq): 
256

recursive_reduce_all function test with initial value: 
257

recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: 
257

recursive_reduce_all function test with execution policy (std::execution::par) and initial value: 
257

recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: 
257

recursive_reduce_all function test with initial value and specified operation: 
65

recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: 
65

recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: 
65

recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: 
65

Play with test_array:

Pure recursive_reduce_all function test: 
4

recursive_reduce_all function test with execution policy (std::execution::seq): 
4

recursive_reduce_all function test with execution policy (std::execution::par): 
4

recursive_reduce_all function test with execution policy (std::execution::par_unseq): 
4

recursive_reduce_all function test with initial value: 
5

recursive_reduce_all function test with execution policy (std::execution::seq) and initial value: 
5

recursive_reduce_all function test with execution policy (std::execution::par) and initial value: 
5

recursive_reduce_all function test with execution policy (std::execution::par_unseq) and initial value: 
5

recursive_reduce_all function test with initial value and specified operation: 
1

recursive_reduce_all function test with execution policy (std::execution::seq), initial value and specified operation: 
1

recursive_reduce_all function test with execution policy (std::execution::par), initial value and specified operation: 
1

recursive_reduce_all function test with execution policy (std::execution::par_unseq), initial value and specified operation: 
1

Computation finished at Thu Jun 29 05:09:13 2023
elapsed time: 0.00150533

Godbolt link is here.

All suggestions are welcome.

The summary information:

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Be more precise with your type constraints

By using incorrect constraints, you greatly reduced the number of types your recursive_reduce_all() works on. std::is_arithmetic_v<> checks whether the type is basically an integer or a float, nothing else will match. There are many more types that std::reduce()'s default binary operator std::plus<> works on.

Note that std::is_arithmetic_v<> doesn't check whether a given type supports arithmetic operations, it only checks if it's a basic integer or floating point type. Even something like std::is_arithmetic_v<std::complex<float>> will be false.

Later on you use std::regular_invocable<BinaryOp, T, T>, which is a much better constraint to use. However, there is also an issue here. Look at std::reduce(), and note how the init parameter can have a different type than the iterator's value type. This allows you, for example, to sum an array of std::uint32_ts into a std::uint64_t without having to worry about integer overflows, or it allows you to have an array of std::chrono::durations and add them all to an initial std::chrono::time_point.

It might be worthwhile to create a concept reducible, that checks whether given an iterator type, init type (defaulting to the iterator's value type) and binary operator (defaulting to std::plus<>), reduction is possible.

So for example:

template<typename T> // No constraint since we're not reducing anything here!
constexpr auto recursive_reduce_all(const T& input)
{
    return input;
}

template<std::ranges::input_range T>
requires (recursive_depth<T>() == 1 && reducible<T>) // = reducible<T, T, std::plus<T>>
constexpr auto recursive_reduce_all(const T& input)
{
    return std::reduce(std::ranges::cbegin(input), std::ranges::cend(input));
}
…
template<std::ranges::input_range T, class TI, class BinaryOp>
requires reducible<recursive_unwrap_type_t<recursive_depth<T>(), T>, TI, BinaryOp>
constexpr auto recursive_reduce_all(const T& input, TI init, BinaryOp binary_op)
{
    …
}
\$\endgroup\$
1
  • \$\begingroup\$ > Note that std::is_arithmetic_v<> doesn't check whether a given type supports arithmetic operations Thank you for mentioning this, it's easy to be misleaded by the name. \$\endgroup\$
    – JimmyHu
    Commented Jul 4, 2023 at 5:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.