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\$

0

Your Answer

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

Browse other questions tagged or ask your own question.