Это дополнительный вопрос для шаблонной функции 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
Все предложения приветствуются.
Сводная информация:
На какой вопрос это продолжение?
Шаблонная функция recursive_transform для случаев бинарных операций в C ++,
Шаблонная функция recursive_transform с уровнем распаковки для произвольной вложенной итерируемой реализации произвольного типа в C ++,
Реализация функции шаблона recursive_transform с концепцией std :: invocable и политикой выполнения в C ++ и
Функция recursive_print для произвольной вложенной итерируемой реализации произвольного типа в C ++
Какие изменения были внесены в код с момента последнего вопроса?
Параметр политики выполнения добавлен в
recursive_transform
шаблонную функцию в этом посте.Почему запрашивается новый обзор?
Если есть какие-то улучшения, пожалуйста, дайте мне знать.