1
\$\begingroup\$

This is a follow-up question for A recursive_reduce Template Function with Unwrap Level Implementation in C++ and A recursive_reduce_string Template Function Implementation in C++. Considering the issue mentioned in G. Sliepen's answer, some modifications are made here:

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

In this post, the updated recursive_reduce_string template function not only support various type of string (std::string, std::wstring, std::u8string, std::pmr::string) but also support custom reduction operator, initial element and parallelism.

The experimental implementation

  • recursive_reduce_string template function implementation

    //  recursive_reduce_string template function
    template<std::size_t base_level, class T>
    constexpr auto recursive_reduce_string(const T& input)
    {
        if (input.empty())
        {
            throw std::runtime_error("input is empty!");
        }
        if constexpr (recursive_depth<T>() == base_level)
        {
            return input;
        }
        else if constexpr (recursive_depth<T>() == base_level + 1)
        {
            auto output = input[0];
            for(int i = 1; i < std::ranges::size(input); ++i)
            {
                output+=input[i];
            }
            return output;
        }
        else
        {
            auto result = recursive_reduce_string<base_level>(
                UL::recursive_transform<recursive_depth<T>() - 2>(
                    input,
                    [](auto&& element){ return recursive_reduce_string<base_level>(element); })
                );
            return result;
        }
    }
    
    //  recursive_reduce_string template function (with execution policy)
    template<std::size_t base_level, class ExPo, class T>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
    constexpr auto recursive_reduce_string(ExPo execution_policy, const T& input)
    {
        if (input.empty())
        {
            throw std::runtime_error("input is empty!");
        }
        if constexpr (recursive_depth<T>() == base_level)
        {
            return input;
        }
        else if constexpr (recursive_depth<T>() == base_level + 1)
        {
            auto output = input[0];
            for(int i = 1; i < std::ranges::size(input); ++i)
            {
                output+=input[i];
            }
            return output;
        }
        else
        {
            auto result = recursive_reduce_string<base_level>(
                execution_policy,
                UL::recursive_transform<recursive_depth<T>() - 2>(
                    execution_policy,
                    input,
                    [&](auto&& element){ return recursive_reduce_string<base_level>(execution_policy, element); })
                );
            return result;
        }
    }
    
    //  recursive_reduce_string template function with initial value
    template<std::size_t base_level, class T, class TI>
    requires (is_summable<recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>, TI>)
    constexpr auto recursive_reduce_string(const T& input, const TI init)
    {
        if (input.empty())
        {
            throw std::runtime_error("input is empty!");
        }
        if constexpr (recursive_depth<T>() == base_level)
        {
            return input;
        }
        else if constexpr (recursive_depth<T>() == base_level + 1)
        {
            auto output = init;
            for(auto&& element : input)
            {
                output+=element;
            }
            return output;
        }
        else
        {
            auto result = recursive_reduce_string<base_level>(
                UL::recursive_transform<recursive_depth<T>() - 2>(
                    input,
                    [&](auto&& element){ return recursive_reduce_string<base_level>(element); }),
                    init
                );
            return result;
        }
    }
    
    //  recursive_reduce_string template function with execution policy and initial value
    template<std::size_t base_level, class ExPo, class T, class TI>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
              is_summable<recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>, TI>)
    constexpr auto recursive_reduce_string(ExPo execution_policy, const T& input, const TI init)
    {
        if (input.empty())
        {
            throw std::runtime_error("input is empty!");
        }
        if constexpr (recursive_depth<T>() == base_level)
        {
            return input;
        }
        else if constexpr (recursive_depth<T>() == base_level + 1)
        {
            auto output = init;
            for(auto&& element : input)
            {
                output+=element;
            }
            return output;
        }
        else
        {
            auto result = recursive_reduce_string<base_level>(
                execution_policy,
                UL::recursive_transform<recursive_depth<T>() - 2>(
                    execution_policy,
                    input,
                    [&](auto&& element){ return recursive_reduce_string<base_level>(execution_policy, element); }),
                    init
                );
            return result;
        }
    }
    
    //  recursive_reduce_string template function with initial value and specified operation
    template<std::size_t base_level, class T, class TI, class BinaryOp>
    requires (  std::regular_invocable<
                BinaryOp,
                recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>,
                recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>>)
    constexpr auto recursive_reduce_string(const T& input, const TI init, BinaryOp binary_op)
    {
        if (input.empty())
        {
            throw std::runtime_error("input is empty!");
        }
        if constexpr (recursive_depth<T>() == base_level)
        {
            return input;
        }
        else if constexpr (recursive_depth<T>() == base_level + 1)
        {
            auto output = init;
            for(auto&& element : input)
            {
                output = std::invoke(binary_op, output, element);
            }
            return output;
        }
        else
        {
            auto result = recursive_reduce_string<base_level>(
                UL::recursive_transform<recursive_depth<T>() - 2>(
                    input,
                    [&](auto&& element){ return recursive_reduce_string<base_level>(element); }),
                    init,
                    binary_op
                );
            return result;
        }
    }
    
    //  recursive_reduce_string template function with execution policy, initial value and specified operation
    template<std::size_t base_level, class ExPo, class T, class TI, class BinaryOp>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
                std::regular_invocable<
                BinaryOp,
                recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>,
                recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>>)
    constexpr auto recursive_reduce_string(ExPo execution_policy, const T& input, const TI init, BinaryOp binary_op)
    {
        if (input.empty())
        {
            throw std::runtime_error("input is empty!");
        }
        if constexpr (recursive_depth<T>() == base_level)
        {
            return input;
        }
        else if constexpr (recursive_depth<T>() == base_level + 1)
        {
            auto output = init;
            for(auto&& element : input)
            {
                output = std::invoke(binary_op, output, element);
            }
            return output;
        }
        else
        {
            auto result = recursive_reduce_string<base_level>(
                execution_policy,
                UL::recursive_transform<recursive_depth<T>() - 2>(
                    execution_policy,
                    input,
                    [&](auto&& element){ return recursive_reduce_string<base_level>(execution_policy, element); }
                    ),
                init,
                binary_op
                );
            return result;
        }
    }
    

Full Testing Code

The full testing code:

#include <algorithm>
#include <array>
#include <chrono>
#include <concepts>
#include <deque>
#include <execution>
#include <experimental/array>
#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);
};

template<class T1, class T2>
concept is_summable = requires(T1 x1, T2 x2) { x1 + x2; };

//  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)>)
            {
                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_string template function
template<std::size_t base_level, class T>
constexpr auto recursive_reduce_string(const T& input)
{
    if (input.empty())
    {
        throw std::runtime_error("input is empty!");
    }
    if constexpr (recursive_depth<T>() == base_level)
    {
        return input;
    }
    else if constexpr (recursive_depth<T>() == base_level + 1)
    {
        auto output = input[0];
        for(int i = 1; i < std::ranges::size(input); ++i)
        {
            output+=input[i];
        }
        return output;
    }
    else
    {
        auto result = recursive_reduce_string<base_level>(
            UL::recursive_transform<recursive_depth<T>() - 2>(
                input,
                [](auto&& element){ return recursive_reduce_string<base_level>(element); })
            );
        return result;
    }
}

//  recursive_reduce_string template function (with execution policy)
template<std::size_t base_level, class ExPo, class T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_reduce_string(ExPo execution_policy, const T& input)
{
    if (input.empty())
    {
        throw std::runtime_error("input is empty!");
    }
    if constexpr (recursive_depth<T>() == base_level)
    {
        return input;
    }
    else if constexpr (recursive_depth<T>() == base_level + 1)
    {
        auto output = input[0];
        for(int i = 1; i < std::ranges::size(input); ++i)
        {
            output+=input[i];
        }
        return output;
    }
    else
    {
        auto result = recursive_reduce_string<base_level>(
            execution_policy,
            UL::recursive_transform<recursive_depth<T>() - 2>(
                execution_policy,
                input,
                [&](auto&& element){ return recursive_reduce_string<base_level>(execution_policy, element); })
            );
        return result;
    }
}

//  recursive_reduce_string template function with initial value
template<std::size_t base_level, class T, class TI>
requires (is_summable<recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>, TI>)
constexpr auto recursive_reduce_string(const T& input, const TI init)
{
    if (input.empty())
    {
        throw std::runtime_error("input is empty!");
    }
    if constexpr (recursive_depth<T>() == base_level)
    {
        return input;
    }
    else if constexpr (recursive_depth<T>() == base_level + 1)
    {
        auto output = init;
        for(auto&& element : input)
        {
            output+=element;
        }
        return output;
    }
    else
    {
        auto result = recursive_reduce_string<base_level>(
            UL::recursive_transform<recursive_depth<T>() - 2>(
                input,
                [&](auto&& element){ return recursive_reduce_string<base_level>(element); }),
                init
            );
        return result;
    }
}

//  recursive_reduce_string template function with execution policy and initial value
template<std::size_t base_level, class ExPo, class T, class TI>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
          is_summable<recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>, TI>)
constexpr auto recursive_reduce_string(ExPo execution_policy, const T& input, const TI init)
{
    if (input.empty())
    {
        throw std::runtime_error("input is empty!");
    }
    if constexpr (recursive_depth<T>() == base_level)
    {
        return input;
    }
    else if constexpr (recursive_depth<T>() == base_level + 1)
    {
        auto output = init;
        for(auto&& element : input)
        {
            output+=element;
        }
        return output;
    }
    else
    {
        auto result = recursive_reduce_string<base_level>(
            execution_policy,
            UL::recursive_transform<recursive_depth<T>() - 2>(
                execution_policy,
                input,
                [&](auto&& element){ return recursive_reduce_string<base_level>(execution_policy, element); }),
                init
            );
        return result;
    }
}

//  recursive_reduce_string template function with initial value and specified operation
template<std::size_t base_level, class T, class TI, class BinaryOp>
requires (  std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>,
            recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>>)
constexpr auto recursive_reduce_string(const T& input, const TI init, BinaryOp binary_op)
{
    if (input.empty())
    {
        throw std::runtime_error("input is empty!");
    }
    if constexpr (recursive_depth<T>() == base_level)
    {
        return input;
    }
    else if constexpr (recursive_depth<T>() == base_level + 1)
    {
        auto output = init;
        for(auto&& element : input)
        {
            output = std::invoke(binary_op, output, element);
        }
        return output;
    }
    else
    {
        auto result = recursive_reduce_string<base_level>(
            UL::recursive_transform<recursive_depth<T>() - 2>(
                input,
                [&](auto&& element){ return recursive_reduce_string<base_level>(element); }),
                init,
                binary_op
            );
        return result;
    }
}

//  recursive_reduce_string template function with execution policy, initial value and specified operation
template<std::size_t base_level, class ExPo, class T, class TI, class BinaryOp>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>> &&
            std::regular_invocable<
            BinaryOp,
            recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>,
            recursive_unwrap_type_t<recursive_depth<T>() - base_level, T>>)
constexpr auto recursive_reduce_string(ExPo execution_policy, const T& input, const TI init, BinaryOp binary_op)
{
    if (input.empty())
    {
        throw std::runtime_error("input is empty!");
    }
    if constexpr (recursive_depth<T>() == base_level)
    {
        return input;
    }
    else if constexpr (recursive_depth<T>() == base_level + 1)
    {
        auto output = init;
        for(auto&& element : input)
        {
            output = std::invoke(binary_op, output, element);
        }
        return output;
    }
    else
    {
        auto result = recursive_reduce_string<base_level>(
            execution_policy,
            UL::recursive_transform<recursive_depth<T>() - 2>(
                execution_policy,
                input,
                [&](auto&& element){ return recursive_reduce_string<base_level>(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 << ":" << "\n";
    std::transform(input.cbegin(), input.cend(), output.begin(), 
        [level](auto&& x)
        {
            std::cout << std::string(level, ' ') << x << "\n";
            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 << ":" << "\n";
    std::transform(input.cbegin(), input.cend(), output.begin(),
        [level](auto&& element)
        {
            return recursive_print(element, level + 1);
        }
    );
    return output;
}

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

    std::cout << "recursive_reduce_string function test with std::array<std::string, 3>:\n";
    std::array<std::string, 3> word_array1 = {"foo", "bar", "baz"};
    std::cout << recursive_reduce_string<1>(word_array1) << '\n';
    std::cout << "recursive_reduce_string function test with std::array<std::array<std::string, 3>, 3>:\n";
    std::array<decltype(word_array1), 3> word_array2 = {word_array1, word_array1, word_array1};
    std::cout << recursive_reduce_string<1>(word_array2) << "\n\n";

    std::cout << "recursive_reduce_string function test with execution policy, std::vector<std::string>:\n";
    std::cout << recursive_reduce_string<1>(std::execution::seq, word_vector1) << '\n';
    std::cout << "recursive_reduce_string function test with execution policy, std::vector<std::vector<std::string>>:\n";
    std::cout << recursive_reduce_string<1>(std::execution::seq, word_vector2) << "\n\n";

    std::cout << "recursive_reduce_string function test with initial value, std::vector<std::string>:\n";
    std::string s1{ "_" };
    std::cout << recursive_reduce_string<1>(word_vector1, s1) << '\n';
    std::cout << "recursive_reduce_string function test with initial value, std::vector<std::vector<std::string>>:\n";
    std::cout << recursive_reduce_string<1>(word_vector2, s1) << "\n\n";

    std::cout << "recursive_reduce_string function test with execution policy, initial value, std::vector<std::string>:\n";
    std::cout << recursive_reduce_string<1>(std::execution::seq, word_vector1, s1) << '\n';
    std::cout << "recursive_reduce_string function test with execution policy, initial value, std::vector<std::vector<std::string>>:\n";
    std::cout << recursive_reduce_string<1>(std::execution::seq, word_vector2, s1) << "\n\n";

    std::cout << "recursive_reduce_string function test with initial value, specified operation, std::vector<std::string>:\n";
    std::cout << recursive_reduce_string<1>(
        word_vector1,
        s1,
        [](std::string x, std::string y) { return x + " " + y; }) << "\n\n";

    std::cout << "recursive_reduce_string function test with execution policy, initial value, specified operation, std::vector<std::string>:\n";
    std::cout << recursive_reduce_string<1>(
        std::execution::seq, 
        word_vector1,
        s1,
        [](std::string x, std::string y) { return x + " " + y; }) << "\n\n";
        

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

    std::cout << "recursive_reduce_string function test with std::vector<std::wstring>:\n";
    std::vector<std::wstring> wstring_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        wstring_vector1.push_back(std::to_wstring(i));
    }
    std::wcout << recursive_reduce_string<1>(wstring_vector1) << '\n';

    std::cout << "recursive_reduce_string function test with std::vector<std::vector<std::wstring>>:\n";
    std::vector<decltype(wstring_vector1)> wstring_vector2{};
    for(int i = 0; i < 4; ++i)
    {
        wstring_vector2.push_back(wstring_vector1);
    }
    std::wcout << recursive_reduce_string<1>(wstring_vector2) << "\n\n";

    std::cout << "recursive_reduce_string function test with std::array<std::wstring, 4>:\n";
    auto wstring_array1 = std::experimental::make_array(
        std::to_wstring(1),
        std::to_wstring(2),
        std::to_wstring(3),
        std::to_wstring(4)
        );
    std::wcout << recursive_reduce_string<1>(wstring_array1) << '\n';
    
    std::cout << "recursive_reduce_string function test with std::array<std::array<std::wstring, 4>, 4>:\n";
    auto wstring_array2 = std::experimental::make_array(
        wstring_array1,
        wstring_array1,
        wstring_array1,
        wstring_array1
        );
    std::wcout << recursive_reduce_string<1>(wstring_array2) << "\n\n";

    std::cout << "recursive_reduce_string function test with std::deque<std::wstring>:\n";
    std::deque<std::wstring> wstring_deque1{};
    for(int i = 0; i < 4; ++i)
    {
        wstring_deque1.push_back(std::to_wstring(i));
    }
    std::wcout << recursive_reduce_string<1>(wstring_vector1) << '\n';

    std::cout << "recursive_reduce_string function test with std::deque<std::deque<std::wstring>>:\n";
    std::deque<decltype(wstring_deque1)> wstring_deque2{};
    for(int i = 0; i < 4; ++i)
    {
        wstring_deque2.push_back(wstring_deque1);
    }
    std::wcout << recursive_reduce_string<1>(wstring_deque2) << "\n\n";
    
    std::cout << "recursive_reduce_string function test with std::vector<std::u8string>:\n";
    std::vector<std::u8string> u8string_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        u8string_vector1.push_back(u8"\u20AC2.00");
    }
    std::cout << reinterpret_cast<const char*>(recursive_reduce_string<1>(u8string_vector1).c_str()) << '\n';

    std::cout << "recursive_reduce_string function test with std::vector<std::vector<std::u8string>>:\n";
    std::vector<std::vector<std::u8string>> u8string_vector2 = {u8string_vector1, u8string_vector1, u8string_vector1};
    std::cout << reinterpret_cast<const char*>(recursive_reduce_string<1>(u8string_vector2).c_str()) << "\n\n";

    std::cout << "recursive_reduce_string function test with std::vector<std::pmr::string>:\n";
    std::pmr::string pmr_string1 = "123";
    std::vector<std::pmr::string> pmr_string_vector1 = {pmr_string1, pmr_string1, pmr_string1};
    std::cout << recursive_reduce_string<1>(pmr_string_vector1) << "\n\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:

recursive_reduce_string function test with std::vector<std::string>:
foobarbazquux
recursive_reduce_string function test with std::vector<std::vector<std::string>>:
foobarbazquuxfoobarbazquuxfoobarbazquux

recursive_reduce_string function test with std::array<std::string, 3>:
foobarbaz
recursive_reduce_string function test with std::array<std::array<std::string, 3>, 3>:
foobarbazfoobarbazfoobarbaz

recursive_reduce_string function test with execution policy, std::vector<std::string>:
foobarbazquux
recursive_reduce_string function test with execution policy, std::vector<std::vector<std::string>>:
foobarbazquuxfoobarbazquuxfoobarbazquux

recursive_reduce_string function test with initial value, std::vector<std::string>:
_foobarbazquux
recursive_reduce_string function test with initial value, std::vector<std::vector<std::string>>:
_foobarbazquuxfoobarbazquuxfoobarbazquux

recursive_reduce_string function test with execution policy, initial value, std::vector<std::string>:
_foobarbazquux
recursive_reduce_string function test with execution policy, initial value, std::vector<std::vector<std::string>>:
_foobarbazquuxfoobarbazquuxfoobarbazquux

recursive_reduce_string function test with initial value, specified operation, std::vector<std::string>:
_ foo bar baz quux

recursive_reduce_string function test with execution policy, initial value, specified operation, std::vector<std::string>:
_ foo bar baz quux

recursive_reduce_string function test with std::deque<std::string>:
1234
recursive_reduce_string function test with std::deque<std::deque<std::string>>:
123412341234

recursive_reduce_string function test with std::vector<std::wstring>:
0123
recursive_reduce_string function test with std::vector<std::vector<std::wstring>>:
0123012301230123

recursive_reduce_string function test with std::array<std::wstring, 4>:
1234
recursive_reduce_string function test with std::array<std::array<std::wstring, 4>, 4>:
1234123412341234

recursive_reduce_string function test with std::deque<std::wstring>:
0123
recursive_reduce_string function test with std::deque<std::deque<std::wstring>>:
0123012301230123

recursive_reduce_string function test with std::vector<std::u8string>:
€2.00€2.00€2.00€2.00
recursive_reduce_string function test with std::vector<std::vector<std::u8string>>:
€2.00€2.00€2.00€2.00€2.00€2.00€2.00€2.00€2.00€2.00€2.00€2.00

recursive_reduce_string function test with std::vector<std::pmr::string>:
123123123

Computation finished at Thu Oct 26 08:44:11 2023
elapsed time: 0.00210049

Godbolt link is here.

All suggestions are welcome.

The summary information:

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

It can reduce more than just strings

Your code is now very generic. In fact, there is nothing in the code referring to strings of any kind. For example, you can sum a vector of integers with it:

std::vector<int> int_vector = {1, 2, 3, 4, 5};
std::cout << recursive_reduce_string<0>(int_vector) << '\n';

So you basically reimplemented your own recursive_reduce() with unwrap level, except instead of saying how many levels to unwrap, now the template parameter says how many levels to leave wrapped.

You should either rename this new function such that it doesn't mention strings anymore, and makes it clear that the parameter is how many levels to leave unwrapped, or alternatively, consider merging this into recursive_reduce(), and maybe use negative values of the template parameter to get the new behavior?

\$\endgroup\$

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.