Шаблонная функция recursive_transform для случаев бинарных операций с политикой выполнения на C ++

Это дополнительный вопрос для шаблонной функции recursive_transform для случаев бинарных операций в C ++, шаблонной функции recursive_transform с уровнем разворачивания для произвольной вложенной итерируемой реализации различного типа в C ++, реализации шаблона функции recursive_transform с концепцией и выполнением std :: invocable Политика в C ++ и функция recursive_print для произвольной вложенной итерируемой реализации произвольного типа в C ++. В параметр политики выполнения доступен с C ++ 17. Я пытаюсь добавить это в recursive_transform шаблонная функция. Учитывая std :: for_each работает с более чем одним диапазоном итераторов, то boost :: zip_iterator здесь используется. Код экспериментальной версии приведен ниже.

Экспериментальная реализация

  • recursive_transform шаблонная функция для случаев бинарных операций с политикой выполнения:

    #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 T1& input1, const T2& input2, const F& f)
    {
        if constexpr (unwrap_level > 0)
        {
            recursive_invoke_result_t2<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, boost::get<0>(elements), boost::get<1>(elements), f);
                    std::lock_guard lock(mutex);
                    output.emplace_back(std::move(result));
                }
            );
    
            return output;
        }
        else
        {
            return f(input1, input2);
        }
    }
    #endif
    
  • recursive_invoke_result2 реализация структуры: чтобы определить тип вывода, recursive_invoke_result2 структура необходима.

    template<typename, typename, typename>
    struct recursive_invoke_result2 { };
    
    template<typename T1, typename T2, std::invocable<T1, T2> F>
    struct recursive_invoke_result2<F, T1, T2> { using type = std::invoke_result_t<F, T1, T2>; };
    
    //  Ref: https://stackoverflow.com/a/66821371/6667035
    template<typename F, class...Ts1, class...Ts2, template<class...>class Container1, template<class...>class Container2>
    requires (
        !std::invocable<F, Container1<Ts1...>, Container2<Ts2...>>&&
        std::ranges::input_range<Container1<Ts1...>>&&
        std::ranges::input_range<Container2<Ts2...>>&&
        requires { typename recursive_invoke_result2<F, std::ranges::range_value_t<Container1<Ts1...>>, std::ranges::range_value_t<Container2<Ts2...>>>::type; })
        struct recursive_invoke_result2<F, Container1<Ts1...>, Container2<Ts2...>>
    {
        using type = Container1<typename recursive_invoke_result2<F, std::ranges::range_value_t<Container1<Ts1...>>, std::ranges::range_value_t<Container2<Ts2...>>>::type>;
    };
    
    template<typename F, typename T1, typename T2>
    using recursive_invoke_result_t2 = typename recursive_invoke_result2<F, T1, T2>::type;
    

Полный код тестирования

//  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<std::ranges::input_range Range>
constexpr auto recursive_print(const Range& input, const int level = 0)
{
    auto output = input;
    std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
    std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
        [level](auto&& x)
        {
            std::cout << std::string(level, ' ') << x << std::endl;
            return x;
        }
    );
    return output;
}

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

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

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

template<typename, typename, typename>
struct recursive_invoke_result2 { };

template<typename T1, typename T2, std::invocable<T1, T2> F>
struct recursive_invoke_result2<F, T1, T2> { using type = std::invoke_result_t<F, T1, T2>; };

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

//  Ref: https://stackoverflow.com/a/66821371/6667035
template<typename F, class...Ts1, class...Ts2, template<class...>class Container1, template<class...>class Container2>
requires (
    !std::invocable<F, Container1<Ts1...>, Container2<Ts2...>>&&
    std::ranges::input_range<Container1<Ts1...>>&&
    std::ranges::input_range<Container2<Ts2...>>&&
    requires { typename recursive_invoke_result2<F, std::ranges::range_value_t<Container1<Ts1...>>, std::ranges::range_value_t<Container2<Ts2...>>>::type; })
    struct recursive_invoke_result2<F, Container1<Ts1...>, Container2<Ts2...>>
{
    using type = Container1<typename recursive_invoke_result2<F, std::ranges::range_value_t<Container1<Ts1...>>, std::ranges::range_value_t<Container2<Ts2...>>>::type>;
};

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

template<typename F, typename T1, typename T2>
using recursive_invoke_result_t2 = typename recursive_invoke_result2<F, T1, T2>::type;

//  recursive_transform implementation (the version with unwrap_level)
template<std::size_t unwrap_level = 1, class T, class F>
constexpr auto recursive_transform(const T& input, const F& f)
{
    if constexpr (unwrap_level > 0)
    {
        recursive_invoke_result_t<F, T> output{};
        std::ranges::transform(
            std::ranges::cbegin(input),
            std::ranges::cend(input),
            std::inserter(output, std::ranges::end(output)),
            [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
        );
        return output;
    }
    else
    {
        return f(input);
    }
}

//  recursive_transform implementation (the version with unwrap_level, with execution policy)
template<std::size_t unwrap_level = 1, class ExPo, class T, class F>
requires (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_invoke_result_t<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, element, f);
                std::lock_guard lock(mutex);
                output.emplace_back(std::move(result));
            }
        );

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

//  recursive_transform for the binary operation cases (the version with unwrap_level)
template<std::size_t unwrap_level = 1, class T1, class T2, class F>
requires (!std::is_execution_policy_v<std::remove_cvref_t<T1>>) //  workaround for gcc: call of overloaded 'recursive_transform<1>(const __pstl::execution::v1::parallel_policy&, std::__cxx11::list<int>&, unary_test_cases_execute_policy()::<lambda(int)>)' is ambiguous
constexpr auto recursive_transform(const T1& input1, const T2& input2, const F& f)
{
    if constexpr (unwrap_level > 0)
    {
        recursive_invoke_result_t2<F, T1, T2> output{};
        std::transform(
            std::ranges::cbegin(input1),
            std::ranges::cend(input1),
            std::ranges::cbegin(input2),
            std::inserter(output, std::ranges::end(output)),
            [&f](auto&& element1, auto&& element2) { return recursive_transform<unwrap_level - 1>(element1, element2, f); }
        );
        return output;
    }
    else
    {
        return f(input1, input2);
    }
}

#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 T1& input1, const T2& input2, const F& f)
{
    if constexpr (unwrap_level > 0)
    {
        recursive_invoke_result_t2<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, boost::get<0>(elements), boost::get<1>(elements), f);
                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()
{
    //  non-nested input test, lambda function applied on input directly
    int test_number = 3;
    std::cout << recursive_transform<0>(test_number, [](auto&& element) { return element + 1; }) << std::endl;

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

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

        std::cout << "std::vector<int> -> std::vector<std::string>: " +
            recursive_transform_result.at(0) << std::endl;                                  //  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>(
                recursive_transform_result,
                [](std::string x) { return std::atoi(x.c_str()); }).at(0) + 1 << std::endl; //  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>(
            test_vector2,
            [](int x)->std::string { return std::to_string(x); }
        );                                                                                  //  For testing

        std::cout << "string: " + recursive_transform_result2.at(0).at(0) << std::endl;     // 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>(
            test_deque,
            [](int x)->std::string { return std::to_string(x); });                          //  For testing

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

        //  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>(
            test_deque2,
            [](int x)->std::string { return std::to_string(x); });                          //  For testing

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

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


        //  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>(
            test_list2,
            [](int x)->std::string { return std::to_string(x); });                          //  For testing
        std::cout << "string: " + recursive_transform_result6.front().front() << std::endl;
        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, test_number, [](auto&& element) { return element + 1; }) << std::endl;

    //  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, test_vector, [](auto element)
        {
            element.push_back(4);
            element.push_back(5);
            return element;
        }).size() << std::endl;

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

    std::cout << "std::vector<int> -> std::vector<std::string>: " +
        recursive_transform_result.at(0) << std::endl;                                  //  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,
            recursive_transform_result,
            [](std::string x) { return std::atoi(x.c_str()); }).at(0) + 1 << std::endl; //  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,
        test_vector2,
        [](int x)->std::string { return std::to_string(x); }
    );                                                                                  //  For testing

    std::cout << "string: " + recursive_transform_result2.at(0).at(0) << std::endl;     // 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,
        test_deque,
        [](int x)->std::string { return std::to_string(x); });                          //  For testing

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

    //  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,
        test_deque2,
        [](int x)->std::string { return std::to_string(x); });                          //  For testing

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

    //  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,
        test_list,
        [](int x)->std::string { return std::to_string(x); });                          //  For testing
    std::cout << "string: " + recursive_transform_result5.front() << std::endl;


    //  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,
        test_list2,
        [](int x)->std::string { return std::to_string(x); });                          //  For testing
    std::cout << "string: " + recursive_transform_result6.front().front() << std::endl;
    return;
}

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

    //  std::vector<std::vector<int>>
    std::vector<decltype(a)> c{ a, a, a }, d{ b, b, b };
    auto result2 = recursive_transform<2>(c, d, [](int element1, int element2) { return element1 + element2; });
    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>(test_deque, test_deque, [](int element1, int element2) { return element1 + element2; });
    for (auto&& element : result3)
    {
        std::cout << element << std::endl;
    }

    //  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>(test_deque2, test_deque2, [](int element1, int element2) { return element1 + element2; });
    recursive_print(result4);

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

    //  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>(test_list2, test_list2, [](int element1, int element2) { return element1 + element2; });
    recursive_print(result6);
    return;
}

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

    //  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, c, d, [](int element1, int element2) { return element1 + element2; });
    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, test_deque, test_deque, [](int element1, int element2) { return element1 + element2; });
    for (auto&& element : result3)
    {
        std::cout << element << std::endl;
    }

    //  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, test_deque2, test_deque2, [](int element1, int element2) { return element1 + element2; });
    recursive_print(result4);

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

    //  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, test_list2, test_list2, [](int element1, int element2) { return element1 + element2; });
    recursive_print(result6);
    return;
}

Результат вышеуказанных тестов:

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

Ссылка Godbolt здесь.

Все предложения приветствуются.

Сводная информация:

  • На какой вопрос это продолжение?

    Шаблонная функция recursive_transform для случаев бинарных операций в C ++,

    Шаблонная функция recursive_transform с уровнем распаковки для произвольной вложенной итерируемой реализации произвольного типа в C ++,

    Реализация функции шаблона recursive_transform с концепцией std :: invocable и политикой выполнения в C ++ и

    Функция recursive_print для произвольной вложенной итерируемой реализации произвольного типа в C ++

  • Какие изменения были внесены в код с момента последнего вопроса?

    Параметр политики выполнения добавлен в recursive_transform шаблонную функцию в этом посте.

  • Почему запрашивается новый обзор?

    Если есть какие-то улучшения, пожалуйста, дайте мне знать.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *