1
\$\begingroup\$

This is a follow-up question for A recursive_transform template function for the binary operation cases in C++, A recursive_transform Template Function with Unwrap Level for Various Type Arbitrary Nested Iterable Implementation in C++, A recursive_transform Template Function Implementation with std::invocable Concept and Execution Policy in C++ and A recursive_print Function For Various Type Arbitrary Nested Iterable Implementation in C++. The execution policy parameter is available since C++17. I am trying to add this into the recursive_transform template function. Considering std::for_each working on more than one range of iterators, the boost::zip_iterator is used here. The experimental version code is as below.

The experimental implementation

  • recursive_transform template function for the binary operation cases with execution policy:

    #define USE_BOOST_ITERATOR
    #ifdef USE_BOOST_ITERATOR
    #include <boost/iterator/zip_iterator.hpp>
    
    //  recursive_transform for the binary operation cases (the version with unwrap_level, with execution policy)
    template<std::size_t unwrap_level = 1, class ExPo, class T1, class T2, class F>
    requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
    constexpr auto recursive_transform(ExPo execution_policy, const F& f, const T1& input1, const T2& input2)
    {
        if constexpr (unwrap_level > 0)
        {
            recursive_variadic_invoke_result_t<unwrap_level, F, T1, T2> output{};
            assert(input1.size() == input2.size());
            std::mutex mutex;
    
            //  Reference: https://stackoverflow.com/a/10457201/6667035
            //  Reference: https://www.boost.org/doc/libs/1_76_0/libs/iterator/doc/zip_iterator.html
            std::for_each(execution_policy,
                boost::make_zip_iterator(
                    boost::make_tuple(std::ranges::cbegin(input1), std::ranges::cbegin(input2))
                ),
                boost::make_zip_iterator(
                    boost::make_tuple(std::ranges::cend(input1), std::ranges::cend(input2))
                ),
                [&](auto&& elements)
                {
                    auto result = recursive_transform<unwrap_level - 1>(execution_policy, f, boost::get<0>(elements), boost::get<1>(elements));
                    std::lock_guard lock(mutex);
                    output.emplace_back(std::move(result));
                }
            );
    
            return output;
        }
        else
        {
            return f(input1, input2);
        }
    }
    #endif
    
  • recursive_variadic_invoke_result_t struct implementation

    //  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<0, F, Container1<Ts1...>, Ts...>
    {
        using type = std::invoke_result_t<F, Container1<Ts1...>, Ts...>;
    };
    
    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;
    

The full testing code

//  A recursive_transform template function for the binary operation cases with execution policy in C++

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

//  recursive_print implementation
template<typename T>
constexpr void recursive_print(const T& input, const int level = 0)
{
    std::cout << std::string(level, ' ') << input << '\n';
}

template<std::ranges::input_range Range>
constexpr void recursive_print(const Range& input, const int level = 0)
{
    std::cout << std::string(level, ' ') << "Level " << level << ":" << "\n";
    std::ranges::for_each(input, [level](auto&& element) {
        recursive_print(element, level + 1);
        });
}

//  recursive_invoke_result_t implementation
template<std::size_t, typename, typename>
struct recursive_invoke_result { };

template<typename T, typename F>
struct recursive_invoke_result<0, F, T> { using type = std::invoke_result_t<F, T>; };

template<std::size_t unwrap_level, typename F, template<typename...> typename Container, typename... Ts>
requires (std::ranges::input_range<Container<Ts...>> &&
          requires { typename recursive_invoke_result<unwrap_level - 1, F, std::ranges::range_value_t<Container<Ts...>>>::type; })
struct recursive_invoke_result<unwrap_level, F, Container<Ts...>>
{
    using type = Container<typename recursive_invoke_result<unwrap_level - 1, F, std::ranges::range_value_t<Container<Ts...>>>::type>;
};

template<std::size_t unwrap_level, typename F, typename T>
using recursive_invoke_result_t = typename recursive_invoke_result<unwrap_level, F, T>::type;

//  recursive_transform implementation (the version with unwrap_level, with execution policy)
template<std::size_t unwrap_level = 1, class ExPo, class F, class T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_transform(ExPo execution_policy, const F& f, const T& input)
{
    if constexpr (unwrap_level > 0)
    {
        recursive_invoke_result_t<unwrap_level, F, T> output{};
        std::mutex mutex;

        //  Reference: https://en.cppreference.com/w/cpp/algorithm/for_each
        std::for_each(execution_policy, input.cbegin(), input.cend(),
            [&](auto&& element)
            {
                auto result = recursive_transform<unwrap_level - 1>(execution_policy, f, element);
                std::lock_guard lock(mutex);
                output.emplace_back(std::move(result));
            }
        );

        return output;
    }
    else
    {
        return f(input);
    }
}

//  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<0, F, Container1<Ts1...>, Ts...>
{
    using type = std::invoke_result_t<F, Container1<Ts1...>, Ts...>;
};

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;

template<typename OutputIt, typename NAryOperation, typename InputIt, typename... InputIts>
OutputIt transform(OutputIt d_first, NAryOperation op, InputIt first, InputIt last, InputIts... rest) {
    while (first != last) {
        *d_first++ = op(*first++, (*rest++)...);
    }
    return d_first;
}

//  recursive_transform for the multiple parameters cases (the version with unwrap_level)
//  Reference: https://stackoverflow.com/a/40701742/6667035
template<std::size_t unwrap_level = 1, class F, class Arg1, class... Args>
constexpr auto recursive_transform(const F& f, const Arg1& arg1, const Args&... args)
{
    if constexpr (unwrap_level > 0)
    {
        recursive_variadic_invoke_result_t<unwrap_level, F, Arg1, Args...> output{};
        transform(
            std::inserter(output, std::ranges::end(output)),
            [&f](auto&& element1, auto&&... elements) { return recursive_transform<unwrap_level - 1>(f, element1, elements...); },
            std::ranges::cbegin(arg1),
            std::ranges::cend(arg1),
            std::ranges::cbegin(args)...
        );
        return output;
    }
    else
    {
        return f(arg1, args...);
    }
}

#define USE_BOOST_ITERATOR
#ifdef USE_BOOST_ITERATOR
#include <boost/iterator/zip_iterator.hpp>

//  recursive_transform for the binary operation cases (the version with unwrap_level, with execution policy)
template<std::size_t unwrap_level = 1, class ExPo, class T1, class T2, class F>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_transform(ExPo execution_policy, const F& f, const T1& input1, const T2& input2)
{
    if constexpr (unwrap_level > 0)
    {
        recursive_variadic_invoke_result_t<unwrap_level, F, T1, T2> output{};
        assert(input1.size() == input2.size());
        std::mutex mutex;

        //  Reference: https://stackoverflow.com/a/10457201/6667035
        //  Reference: https://www.boost.org/doc/libs/1_76_0/libs/iterator/doc/zip_iterator.html
        std::for_each(execution_policy,
            boost::make_zip_iterator(
                boost::make_tuple(std::ranges::cbegin(input1), std::ranges::cbegin(input2))
            ),
            boost::make_zip_iterator(
                boost::make_tuple(std::ranges::cend(input1), std::ranges::cend(input2))
            ),
            [&](auto&& elements)
            {
                auto result = recursive_transform<unwrap_level - 1>(execution_policy, f, boost::get<0>(elements), boost::get<1>(elements));
                std::lock_guard lock(mutex);
                output.emplace_back(std::move(result));
            }
        );

        return output;
    }
    else
    {
        return f(input1, input2);
    }
}
#endif

void unary_test_cases();
void unary_test_cases_execute_policy();
void binary_test_cases();
void binary_test_cases_execute_policy();

int main()
{
    unary_test_cases();
    unary_test_cases_execute_policy();
    binary_test_cases();
    binary_test_cases_execute_policy();
    return 0;
}

void unary_test_cases()
{
    std::cout << "*****unary_test_cases*****" << "\n";

    //  non-nested input test, lambda function applied on input directly
    int test_number = 3;
    std::cout << recursive_transform<0>([](auto&& element) { return element + 1; }, test_number) << "\n";

    //  nested input test, lambda function applied on input directly
    std::vector<int> test_vector = {
        1, 2, 3
    };
    std::cout << recursive_transform<0>([](auto element)
        {
            element.push_back(4);
            element.push_back(5);
            return element;
        },
        test_vector).size() << "\n";
    
    //  std::vector<int> -> std::vector<std::string>
    auto recursive_transform_result = recursive_transform<1>(
        [](int x)->std::string { return std::to_string(x); },
        test_vector
    );                                                                              //  For testing
    std::cout << "std::vector<int> -> std::vector<std::string>: " +
        recursive_transform_result.at(0) << "\n";                                  //  recursive_transform_result.at(0) is a std::string
    
    //  std::vector<string> -> std::vector<int>
    std::cout << "std::vector<string> -> std::vector<int>: "
        << recursive_transform<1>(
            [](std::string x) { return std::atoi(x.c_str()); },
            recursive_transform_result).at(0) + 1 << "\n";                         //  std::string element to int
    
    //  std::vector<std::vector<int>> -> std::vector<std::vector<std::string>>
    std::vector<decltype(test_vector)> test_vector2 = {
        test_vector, test_vector, test_vector
    };

    auto recursive_transform_result2 = recursive_transform<2>(
        [](int x)->std::string { return std::to_string(x); },
        test_vector2
    );                                                                             //  For testing
    std::cout << "string: " + recursive_transform_result2.at(0).at(0) << "\n";     // recursive_transform_result.at(0).at(0) is also a std::string
    
    //  std::deque<int> -> std::deque<std::string>
    std::deque<int> test_deque;
    test_deque.push_back(1);
    test_deque.push_back(1);
    test_deque.push_back(1);

    auto recursive_transform_result3 = recursive_transform<1>(
        [](int x)->std::string { return std::to_string(x); },
        test_deque);                          //  For testing

    std::cout << "string: " + recursive_transform_result3.at(0) << "\n";
    
    //  std::deque<std::deque<int>> -> std::deque<std::deque<std::string>>
    std::deque<decltype(test_deque)> test_deque2;
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);

    auto recursive_transform_result4 = recursive_transform<2>(
        [](int x)->std::string { return std::to_string(x); },
        test_deque2);                          //  For testing

    std::cout << "string: " + recursive_transform_result4.at(0).at(0) << "\n";

    //  std::list<int> -> std::list<std::string>
    std::list<int> test_list = { 1, 2, 3, 4 };
    auto recursive_transform_result5 = recursive_transform<1>(
        [](int x)->std::string { return std::to_string(x); },
        test_list);                          //  For testing
    std::cout << "string: " + recursive_transform_result5.front() << "\n";


    //  std::list<std::list<int>> -> std::list<std::list<std::string>>
    std::list<std::list<int>> test_list2 = { test_list, test_list, test_list, test_list };
    auto recursive_transform_result6 = recursive_transform<2>(
        [](int x)->std::string { return std::to_string(x); },
        test_list2);                          //  For testing
    std::cout << "string: " + recursive_transform_result6.front().front() << "\n";
    return;
}

void unary_test_cases_execute_policy()
{
    //  non-nested input test, lambda function applied on input directly
    int test_number = 3;
    std::cout << recursive_transform<0>(
        std::execution::par,
        [](auto&& element) { return element + 1; },
        test_number) << "\n";

    //  nested input test, lambda function applied on input directly
    std::vector<int> test_vector = {
        1, 2, 3
    };
    std::cout << recursive_transform<0>(std::execution::par,
        [](auto element)
        {
            element.push_back(4);
            element.push_back(5);
            return element;
        },
        test_vector).size() << "\n";

    //  std::vector<int> -> std::vector<std::string>
    auto recursive_transform_result = recursive_transform<1>(
        std::execution::par,
        [](int x)->std::string { return std::to_string(x); },
        test_vector
    );                                                                                  //  For testing

    std::cout << "std::vector<int> -> std::vector<std::string>: " +
        recursive_transform_result.at(0) << "\n";                                  //  recursive_transform_result.at(0) is a std::string

    //  std::vector<string> -> std::vector<int>
    std::cout << "std::vector<string> -> std::vector<int>: "
        << recursive_transform<1>(
            std::execution::par,
            [](std::string x) { return std::atoi(x.c_str()); },
            recursive_transform_result).at(0) + 1 << "\n"; //  std::string element to int

    //  std::vector<std::vector<int>> -> std::vector<std::vector<std::string>>
    std::vector<decltype(test_vector)> test_vector2 = {
        test_vector, test_vector, test_vector
    };

    auto recursive_transform_result2 = recursive_transform<2>(
        std::execution::par,
        [](int x)->std::string { return std::to_string(x); },
        test_vector2
    );                                                                                  //  For testing

    std::cout << "string: " + recursive_transform_result2.at(0).at(0) << "\n";     // recursive_transform_result.at(0).at(0) is also a std::string

    //  std::deque<int> -> std::deque<std::string>
    std::deque<int> test_deque;
    test_deque.push_back(1);
    test_deque.push_back(1);
    test_deque.push_back(1);

    auto recursive_transform_result3 = recursive_transform<1>(
        std::execution::par,
        [](int x)->std::string { return std::to_string(x); },
        test_deque);                          //  For testing

    std::cout << "string: " + recursive_transform_result3.at(0) << "\n";

    //  std::deque<std::deque<int>> -> std::deque<std::deque<std::string>>
    std::deque<decltype(test_deque)> test_deque2;
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);

    auto recursive_transform_result4 = recursive_transform<2>(
        std::execution::par,
        [](int x)->std::string { return std::to_string(x); },
        test_deque2);                          //  For testing

    std::cout << "string: " + recursive_transform_result4.at(0).at(0) << "\n";

    //  std::list<int> -> std::list<std::string>
    std::list<int> test_list = { 1, 2, 3, 4 };
    auto recursive_transform_result5 = recursive_transform<1>(
        std::execution::par,
        [](int x)->std::string { return std::to_string(x); },
        test_list);                          //  For testing
    std::cout << "string: " + recursive_transform_result5.front() << "\n";


    //  std::list<std::list<int>> -> std::list<std::list<std::string>>
    std::list<std::list<int>> test_list2 = { test_list, test_list, test_list, test_list };
    auto recursive_transform_result6 = recursive_transform<2>(
        std::execution::par,
        [](int x)->std::string { return std::to_string(x); },
        test_list2);                          //  For testing
    std::cout << "string: " + recursive_transform_result6.front().front() << "\n";
    return;
}

void binary_test_cases()
{
    std::cout << "*****binary_test_cases*****" << "\n";

    //  non-nested input test, lambda function applied on input directly
    int test_number1 = 3, test_number2 = 4;
    std::cout << recursive_transform<0>(
        [](auto&& element1, auto&& element2) { return element1 + element2; },
        test_number1, test_number2) << "\n";
    
    //  std::vector<int>
    std::cout << "std::vector<int>" << "\n";
    std::vector<int> a{ 1, 2, 3 }, b{ 4, 5, 6 };
    auto result1 = recursive_transform<1>([](int element1, int element2) { return element1 + element2; }, a, b);
    for (auto&& element : result1)
    {
        std::cout << element << "\n";
    }
    
    //  std::vector<std::vector<int>>
    std::vector<decltype(a)> c{ a, a, a }, d{ b, b, b };
    auto result2 = recursive_transform<2>([](int element1, int element2) { return element1 + element2; }, c, d);
    recursive_print(result2);

    //  std::deque<int>
    std::deque<int> test_deque;
    test_deque.push_back(1);
    test_deque.push_back(1);
    test_deque.push_back(1);

    auto result3 = recursive_transform<1>(
        [](int element1, int element2) { return element1 + element2; },
        test_deque, test_deque);
    for (auto&& element : result3)
    {
        std::cout << element << "\n";
    }

    //  std::deque<std::deque<int>>
    std::deque<decltype(test_deque)> test_deque2;
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);
    auto result4 = recursive_transform<2>(
        [](int element1, int element2) { return element1 + element2; },
        test_deque2, test_deque2);
    recursive_print(result4);

    //  std::list<int>
    std::list<int> test_list = { 1, 2, 3, 4 };
    auto result5 = recursive_transform<1>(
        [](int element1, int element2) { return element1 + element2; },
        test_list, test_list);
    for (auto&& element : result5)
    {
        std::cout << element << "\n";
    }

    //  std::list<std::list<int>>
    std::list<std::list<int>> test_list2 = { test_list, test_list, test_list, test_list };
    auto result6 = recursive_transform<2>(
        [](int element1, int element2) { return element1 + element2; },
        test_list2, test_list2);
    recursive_print(result6);
    return;
}

void binary_test_cases_execute_policy()
{
    std::cout << "binary_test_cases_execute_policy" << "\n";

    //  std::vector<int>
    std::vector<int> a{ 1, 2, 3 }, b{ 4, 5, 6 };
    auto result1 = recursive_transform<1>(
        std::execution::par,
        [](int element1, int element2) { return element1 + element2; },
        a, b);
    for (auto&& element : result1)
    {
        std::cout << element << "\n";
    }

    //  std::vector<std::vector<int>>
    std::vector<decltype(a)> c{ a, a, a }, d{ b, b, b };
    auto result2 = recursive_transform<2>(
        std::execution::par,
        [](int element1, int element2) { return element1 + element2; },
        c, d);
    recursive_print(result2);

    //  std::deque<int>
    std::deque<int> test_deque;
    test_deque.push_back(1);
    test_deque.push_back(1);
    test_deque.push_back(1);

    auto result3 = recursive_transform<1>(
        std::execution::par,
        [](int element1, int element2) { return element1 + element2; },
        test_deque, test_deque);
    for (auto&& element : result3)
    {
        std::cout << element << "\n";
    }

    //  std::deque<std::deque<int>>
    std::deque<decltype(test_deque)> test_deque2;
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);
    test_deque2.push_back(test_deque);
    auto result4 = recursive_transform<2>(
        std::execution::par,
        [](int element1, int element2) { return element1 + element2; },
        test_deque2, test_deque2);
    recursive_print(result4);

    //  std::list<int>
    std::list<int> test_list = { 1, 2, 3, 4 };
    auto result5 = recursive_transform<1>(
        std::execution::par,
        [](int element1, int element2) { return element1 + element2; },
        test_list, test_list);
    for (auto&& element : result5)
    {
        std::cout << element << "\n";
    }

    //  std::list<std::list<int>>
    std::list<std::list<int>> test_list2 = { test_list, test_list, test_list, test_list };
    auto result6 = recursive_transform<2>(
        std::execution::par,
        [](int element1, int element2) { return element1 + element2; },
        test_list2, test_list2);
    recursive_print(result6);
    return;
}

The output of the above tests:

*****unary_test_cases*****
4
5
std::vector<int> -> std::vector<std::string>: 1
std::vector<string> -> std::vector<int>: 2
string: 1
string: 1
string: 1
string: 1
string: 1
4
5
std::vector<int> -> std::vector<std::string>: 1
std::vector<string> -> std::vector<int>: 2
string: 1
string: 1
string: 1
string: 1
string: 1
*****binary_test_cases*****
7
std::vector<int>
5
7
9
Level 0:
 Level 1:
  5
  7
  9
 Level 1:
  5
  7
  9
 Level 1:
  5
  7
  9
2
2
2
Level 0:
 Level 1:
  2
  2
  2
 Level 1:
  2
  2
  2
 Level 1:
  2
  2
  2
2
4
6
8
Level 0:
 Level 1:
  2
  4
  6
  8
 Level 1:
  2
  4
  6
  8
 Level 1:
  2
  4
  6
  8
 Level 1:
  2
  4
  6
  8
binary_test_cases_execute_policy
5
7
9
Level 0:
 Level 1:
  5
  7
  9
 Level 1:
  5
  7
  9
 Level 1:
  5
  7
  9
2
2
2
Level 0:
 Level 1:
  2
  2
  2
 Level 1:
  2
  2
  2
 Level 1:
  2
  2
  2
2
4
6
8
Level 0:
 Level 1:
  2
  4
  6
  8
 Level 1:
  2
  4
  6
  8
 Level 1:
  2
  4
  6
  8
 Level 1:
  2
  4
  6
  8

A Godbolt link is here.

All suggestions are welcome.

The summary information:

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.