1
\$\begingroup\$

This is a follow-up question for A recursive_sum Template Function Implementation with Unwrap Level in C++, A recursive_reduce_all Template Function Implementation in C++, A recursive_reduce Template Function with Unwrap Level Implementation in C++ and A recursive_depth Function Implementation with Target Type Parameter in C++. Considering the answer provided by G. Sliepen, std::string test cases are mentioned. For dealing with these nested std::string cases, recursive_reduce_string template function is introduced in this post.

The experimental implementation

  • recursive_reduce_string template function implementation

    //  recursive_reduce_string template function
    template<class T>
    requires(std::same_as<T, std::string>)
    constexpr auto recursive_reduce_string(const T& input1)
    {
        return input1;
    }
    
    template<std::ranges::input_range T>
    requires (std::same_as<recursive_unwrap_type_t<recursive_depth<T>() - 1, T>, std::string> &&
              recursive_depth<T>() - 1 == 1)
    constexpr auto recursive_reduce_string(const T& input)
    {
        auto output = input.at(0);
        for(int i = 1; i < std::ranges::size(input); i++)
        {
            output+=input.at(i);
        }
        return output;
    }
    
    template<std::ranges::input_range T>
    constexpr auto recursive_reduce_string(const T& input)
    {
        auto result = recursive_reduce_string(
            UL::recursive_transform<recursive_depth<T>() - 2>(
                input,
                [](auto&& element){ return recursive_reduce_string(element); })
            );
        return result;
    }
    

Full Testing Code

The full testing code:

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

#include <algorithm>
#include <array>
#include <chrono>
#include <concepts>
#include <deque>
#include <execution>
#include <iostream>
#include <queue>
#include <ranges>
#include <string>
#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);
};

//  has_arithmetic_operations concept
template<class T>
concept has_arithmetic_operations = requires(T input)
{
    std::plus<>{}(input, input);
    std::minus<>{}(input, input);
    std::multiplies<>{}(input, input);
    std::divides<>{}(input, input);
};

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

template<std::ranges::input_range Range>
constexpr std::size_t recursive_depth()
{
    return recursive_depth<std::ranges::range_value_t<Range>>() + std::size_t{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_array_invoke_result implementation
template<std::size_t, typename, typename, typename...>
struct recursive_array_invoke_result { };

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

template<   std::size_t unwrap_level,
            typename F, 
            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_invoke_result<
                                    unwrap_level - 1,
                                    F,
                                    std::ranges::range_value_t<Container<T, N>>>::type; })                //  The rest arguments are ranges
struct recursive_array_invoke_result<unwrap_level, F, Container<T, N>>
{
    using type = Container<
        typename recursive_array_invoke_result<
        unwrap_level - 1,
        F,
        std::ranges::range_value_t<Container<T, N>>
        >::type, N>;
};

template<   std::size_t unwrap_level,
            typename F,
            template<class, std::size_t> class Container,
            typename T,
            std::size_t N>
using recursive_array_invoke_result_t = typename recursive_array_invoke_result<unwrap_level, F, Container<T, N>>::type;

//  recursive_unwrap_type_t struct implementation, https://codereview.stackexchange.com/q/284610/231235
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,
                class Proj = std::identity>
    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, Proj proj = {} )
    {
        if constexpr (unwrap_level > 0)
        {
            auto output = clone_empty_container(input, f);
            if constexpr (is_reservable<decltype(output)> &&
                          is_sized<decltype(input)> &&
                          std::indirectly_writable<decltype(output),
                            std::indirect_result_t<F&, std::projected<std::ranges::iterator_t<T>, Proj>>>)
            {
                output.reserve(input.size());
                std::ranges::transform(
                    input,
                    std::ranges::begin(output),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); },
                    proj
                );
            }
            else
            {
                std::ranges::transform(
                    input,
                    std::inserter(output, std::ranges::end(output)),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); },
                    proj
                );
            }
            return output;
        }
        else if constexpr(std::regular_invocable<F, T>)
        {
            return std::invoke(f, std::invoke(proj, 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)
    {
        recursive_array_invoke_result_t<unwrap_level, F, Container, T, N> output{};

        if constexpr (unwrap_level > 1)
        {
            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::ranges::begin(output),
                        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 (unwrap_level <= recursive_depth<T>()&&        //  handling incorrect unwrap levels more gracefully, https://codereview.stackexchange.com/a/283563/231235
              !std::ranges::view<T>)
    constexpr auto recursive_transform(const T& input, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            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, std::copy_constructible 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 std::invoke(f, input);
        }
    }

    //  recursive_transform implementation (binary case, the version with unwrap_level)
    template<std::size_t unwrap_level = 1, class ExPo, std::ranges::input_range R1, std::ranges::input_range R2, std::copy_constructible F>
    constexpr auto recursive_transform(ExPo execution_policy, const R1& input1, const R2& input2, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            recursive_variadic_invoke_result_t<unwrap_level, F, R1> output{};
            output.resize(input1.size());
            std::mutex mutex;
            std::transform(execution_policy, std::ranges::cbegin(input1), std::ranges::cend(input1), std::ranges::cbegin(input2), std::ranges::begin(output),
                [&](auto&& element1, auto&& element2)
                {
                    std::lock_guard lock(mutex);
                    return recursive_transform<unwrap_level - 1>(execution_policy, element1, element2, f);
                });
            return output;
        }
        else
        {
            return std::invoke(f, input1, input2);
        }
    }
}

/*  recursive_reduce_all template function performs operation on input container exhaustively
https://codereview.stackexchange.com/a/285831/231235
*/
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 (has_arithmetic_operations<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 (has_arithmetic_operations<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 (has_arithmetic_operations<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, has_arithmetic_operations 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>> &&
          has_arithmetic_operations<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>> &&
          has_arithmetic_operations<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>> &&
          has_arithmetic_operations<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<has_arithmetic_operations T>
constexpr auto recursive_reduce_all(const T& input1, const T& input2)
{
    return input1 + input2;
}

template<std::ranges::input_range T, class TI>
requires (has_arithmetic_operations<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 (has_arithmetic_operations<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 (has_arithmetic_operations<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, has_arithmetic_operations 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>> &&
          has_arithmetic_operations<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>> &&
          has_arithmetic_operations<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>> &&
          has_arithmetic_operations<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<has_arithmetic_operations 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 (has_arithmetic_operations<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 (has_arithmetic_operations<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 (has_arithmetic_operations<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, has_arithmetic_operations 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>> &&
          has_arithmetic_operations<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>> &&
          has_arithmetic_operations<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>> &&
          has_arithmetic_operations<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<std::size_t wrapped_level = 0, class T>
constexpr auto get_wrapped_first_element(const T& input)
{
    if constexpr (wrapped_level > 0)
    {
        return get_wrapped_first_element<wrapped_level - 1>(input.at(0));
    }
    else
    {
        return input;
    }
}

//  recursive_reduce_string template function
template<class T>
requires(std::same_as<T, std::string>)
constexpr auto recursive_reduce_string(const T& input1)
{
    return input1;
}

template<std::ranges::input_range T>
requires (std::same_as<recursive_unwrap_type_t<recursive_depth<T>() - 1, T>, std::string> &&
          recursive_depth<T>() - 1 == 1)
constexpr auto recursive_reduce_string(const T& input)
{
    auto output = input.at(0);
    for(int i = 1; i < std::ranges::size(input); i++)
    {
        output+=input.at(i);
    }
    return output;
}

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

void recursive_reduce_string_tests()
{
    std::cout << "Play with std::vectors:\n";

    std::vector<std::string> word_vector1 = {"foo", "bar", "baz", "quux"};
    std::cout << recursive_reduce_string(word_vector1) << '\n';
    
    std::vector<std::vector<std::string>> word_vector2 = {word_vector1, word_vector1, word_vector1};
    std::cout << recursive_reduce_string(word_vector2) << '\n';

    std::cout << "Play with std::deque:\n";
    std::deque<std::string> word_deque1 = {"1", "2", "3", "4"};
    std::cout << recursive_reduce_string(word_deque1) << '\n';

    std::deque<std::deque<std::string>> word_deque2 = {word_deque1, word_deque1, word_deque1};
    std::cout << recursive_reduce_string(word_deque2) << '\n';

    return;
}

int main()
{
    auto start = std::chrono::system_clock::now();
    recursive_reduce_string_tests();
    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 std::vectors:
foobarbazquux
foobarbazquuxfoobarbazquuxfoobarbazquux
Play with std::deque:
1234
123412341234
Computation finished at Tue Oct 17 08:31:04 2023
elapsed time: 0.00128089

Godbolt link is here.

All suggestions are welcome.

The summary information:

\$\endgroup\$
1
  • \$\begingroup\$ Why so many irrelevant #includes? \$\endgroup\$
    – vnp
    Commented Oct 17, 2023 at 3:33

1 Answer 1

1
\$\begingroup\$

There are several issues with this code:

  • Why are you reimplementing a recursive reduce when you already have UL::recursive_reduce()?
  • It is not generic enough. Even if you only wanted it to be limited to strings, consider that std::string is just a type alias for std::basic_string<char>. What about std::wstring, std::u8string, std::pmr::string, std::string_view, C strings and all other kinds of strings?
  • The name implies it is similar to std::reduce(), however it doesn't support a custom reduction operator, doesn't support an initial element, and doesn't support parallelism. If you only want to join strings, then recursive_join() or recursive_concat() would be better names for this function.
  • It will throw an exception if any of the containers is empty.
  • It unnecessarily uses .at() in the for-loop.

I would ensure you have a recursive reduce function that has the necessary (template) parameters to do what you want, such that recursive_reduce_string() only needs to be an alias for the generic reduce function.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you so much for answering and pointing out these issues. I'll try to revise my code. About the first point, with checking recursive_reduce implementation, I found that the base case is specified to non-range input. However, std::string is a kind of range. \$\endgroup\$
    – JimmyHu
    Commented Oct 18, 2023 at 11:36
  • \$\begingroup\$ But you already made variants that either take a depth or a type to stop at, right? \$\endgroup\$
    – G. Sliepen
    Commented Oct 18, 2023 at 13:13

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.