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
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
A recursive_transform template function for the binary operation cases in C++,
A recursive_print Function For Various Type Arbitrary Nested Iterable Implementation in C++
What changes has been made in the code since last question?
The execution policy parameter has been added into the
recursive_transform
template function in this post.Why a new review is being asked for?
If there is any possible improvement, please let me know.