This is a follow-up question for A recursive_replace_copy_if Template Function Implementation in C++. I am trying to implement recursive_replace_copy_if template function with unwrap level in this post.
The experimental implementation
recursive_replace_copy_ifTemplate Function Implementation// recursive_replace_copy_if template function implementation template<std::size_t unwrap_level = 1, std::ranges::input_range Range, class UnaryPredicate, class T> requires(unwrap_level <= recursive_depth<Range>() && (recursive_invocable<unwrap_level, UnaryPredicate, Range>)) constexpr auto recursive_replace_copy_if(const Range& input, const UnaryPredicate& unary_predicate, const T& new_value) { if constexpr(unwrap_level == 1) { Range output{}; std::ranges::replace_copy_if( std::ranges::cbegin(input), std::ranges::cend(input), std::inserter(output, std::ranges::end(output)), unary_predicate, new_value); return output; } else { Range output{}; std::ranges::transform( std::ranges::cbegin(input), std::ranges::cend(input), std::inserter(output, std::ranges::end(output)), [&](auto&& element) { return recursive_replace_copy_if<unwrap_level - 1>(element, unary_predicate, new_value); } ); return output; } }
Full Testing Code
The full testing code:
// A recursive_replace_copy_if Template Function with Unwrap Level Implementation in C++
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <complex>
#include <concepts>
#include <deque>
#include <exception>
#include <execution>
#include <functional>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
// Copy from https://stackoverflow.com/a/37264642/6667035
#ifndef NDEBUG
# define M_Assert(Expr, Msg) \
M_Assert_Helper(#Expr, Expr, __FILE__, __LINE__, Msg)
#else
# define M_Assert(Expr, Msg) ;
#endif
void M_Assert_Helper(const char* expr_str, bool expr, const char* file, int line, const char* msg)
{
if (!expr)
{
std::cerr << "Assert failed:\t" << msg << "\n"
<< "Expected:\t" << expr_str << "\n"
<< "Source:\t\t" << file << ", line " << line << "\n";
abort();
}
}
template<typename T>
concept is_inserterable = requires(T x)
{
std::inserter(x, std::ranges::end(x));
};
#ifdef USE_BOOST_MULTIDIMENSIONAL_ARRAY
template<typename T>
concept is_multi_array = requires(T x)
{
x.num_dimensions();
x.shape();
boost::multi_array(x);
};
#endif
// recursive_depth function implementation
template<typename T>
constexpr std::size_t recursive_depth()
{
return 0;
}
template<std::ranges::input_range Range>
constexpr std::size_t recursive_depth()
{
return recursive_depth<std::ranges::range_value_t<Range>>() + 1;
}
// recursive_copy_if function
template <std::ranges::input_range Range, std::invocable<std::ranges::range_value_t<Range>> UnaryPredicate>
constexpr auto recursive_copy_if(const Range& input, const UnaryPredicate& unary_predicate)
{
Range output{};
std::ranges::copy_if(std::ranges::cbegin(input), std::ranges::cend(input),
std::inserter(output, std::ranges::end(output)),
unary_predicate);
return output;
}
template <
std::ranges::input_range Range,
class UnaryPredicate>
constexpr auto recursive_copy_if(const Range& input, const UnaryPredicate& unary_predicate)
{
Range output{};
std::ranges::transform(
std::ranges::cbegin(input),
std::ranges::cend(input),
std::inserter(output, std::ranges::end(output)),
[&unary_predicate](auto&& element) { return recursive_copy_if(element, unary_predicate); }
);
return output;
}
// recursive_count implementation
// recursive_count implementation (the version with unwrap_level)
template<std::size_t unwrap_level, class T>
constexpr auto recursive_count(const T& input, const auto& target)
{
if constexpr (unwrap_level > 0)
{
static_assert(unwrap_level <= recursive_depth<T>(),
"unwrap level higher than recursion depth of input");
return std::transform_reduce(std::ranges::cbegin(input), std::ranges::cend(input), std::size_t{}, std::plus<std::size_t>(), [&target](auto&& element) {
return recursive_count<unwrap_level - 1>(element, target);
});
}
else
{
return (input == target) ? 1 : 0;
}
}
// recursive_count implementation (the version without unwrap_level)
template<std::ranges::input_range Range>
constexpr auto recursive_count(const Range& input, const auto& target)
{
return recursive_count<recursive_depth<Range>()>(input, target);
}
// recursive_count implementation (with execution policy)
template<class ExPo, std::ranges::input_range Range, typename T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr auto recursive_count(ExPo execution_policy, const Range& input, const T& target)
{
return std::count(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), target);
}
template<class ExPo, std::ranges::input_range Range, typename T>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>) && (std::ranges::input_range<std::ranges::range_value_t<Range>>)
constexpr auto recursive_count(ExPo execution_policy, const Range& input, const T& target)
{
return std::transform_reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), std::size_t{}, std::plus<std::size_t>(), [execution_policy, target](auto&& element) {
return recursive_count(execution_policy, element, target);
});
}
// recursive_count_if implementation
template<class T, std::invocable<T> Pred>
constexpr std::size_t recursive_count_if(const T& input, const Pred& predicate)
{
return predicate(input) ? 1 : 0;
}
template<std::ranges::input_range Range, class Pred>
requires (!std::invocable<Pred, Range>)
constexpr auto recursive_count_if(const Range& input, const Pred& predicate)
{
return std::transform_reduce(std::ranges::cbegin(input), std::ranges::cend(input), std::size_t{}, std::plus<std::size_t>(), [predicate](auto&& element) {
return recursive_count_if(element, predicate);
});
}
// recursive_count_if implementation (with execution policy)
template<class ExPo, class T, std::invocable<T> Pred>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr std::size_t recursive_count_if(ExPo execution_policy, const T& input, const Pred& predicate)
{
return predicate(input) ? 1 : 0;
}
template<class ExPo, std::ranges::input_range Range, class Pred>
requires ((std::is_execution_policy_v<std::remove_cvref_t<ExPo>>) && (!std::invocable<Pred, Range>))
constexpr auto recursive_count_if(ExPo execution_policy, const Range& input, const Pred& predicate)
{
return std::transform_reduce(execution_policy, std::ranges::cbegin(input), std::ranges::cend(input), std::size_t{}, std::plus<std::size_t>(), [predicate](auto&& element) {
return recursive_count_if(element, predicate);
});
}
// recursive_count_if implementation (the version with unwrap_level)
template<std::size_t unwrap_level, std::ranges::range T, class Pred>
auto recursive_count_if(const T& input, const Pred& predicate)
{
if constexpr (unwrap_level > 1)
{
return std::transform_reduce(std::ranges::cbegin(input), std::ranges::cend(input), std::size_t{}, std::plus<std::size_t>(), [predicate](auto&& element) {
return recursive_count_if<unwrap_level - 1>(element, predicate);
});
}
else
{
return std::count_if(std::ranges::cbegin(input), std::ranges::cend(input), predicate);
}
}
// recursive_print struct implementation
struct recursive_print_fn
{
template<std::ranges::input_range T>
constexpr auto operator()(const T& input, const int level = 0) const
{
T output = input;
std::cout << std::string(level, ' ') << "Level " << level << ":" << "\n";
std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
[&](auto&& x)
{
std::cout << std::string(level, ' ') << x << "\n";
return x;
}
);
return output;
}
template<std::ranges::input_range T>
requires (std::ranges::input_range<std::ranges::range_value_t<T>>)
constexpr auto operator()(const T& input, const int level = 0) const
{
T output = input;
std::cout << std::string(level, ' ') << "Level " << level << ":" << "\n";
std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
[&](auto&& element)
{
return operator()(element, level + 1);
}
);
return output;
}
};
inline constexpr recursive_print_fn recursive_print;
// is_recursive_invocable template function implementation
template<std::size_t unwrap_level, class F, class... T>
requires(unwrap_level <= recursive_depth<T...>())
static constexpr bool is_recursive_invocable()
{
if constexpr (unwrap_level == 0) {
return std::invocable<F, T...>;
} else {
return is_recursive_invocable<
unwrap_level - 1,
F,
std::ranges::range_value_t<T>...>();
}
}
// recursive_invocable concept
template<std::size_t unwrap_level, class F, class... T>
concept recursive_invocable =
is_recursive_invocable<unwrap_level, F, T...>();
// recursive_replace_copy_if template function implementation
template<std::size_t unwrap_level = 1, std::ranges::input_range Range, class UnaryPredicate, class T>
requires(unwrap_level <= recursive_depth<Range>() &&
(recursive_invocable<unwrap_level, UnaryPredicate, Range>))
constexpr auto recursive_replace_copy_if(const Range& input, const UnaryPredicate& unary_predicate, const T& new_value)
{
if constexpr(unwrap_level == 1)
{
Range output{};
std::ranges::replace_copy_if(
std::ranges::cbegin(input),
std::ranges::cend(input),
std::inserter(output, std::ranges::end(output)),
unary_predicate,
new_value);
return output;
}
else
{
Range output{};
std::ranges::transform(
std::ranges::cbegin(input),
std::ranges::cend(input),
std::inserter(output, std::ranges::end(output)),
[&](auto&& element) { return recursive_replace_copy_if<unwrap_level - 1>(element, unary_predicate, new_value); }
);
return output;
}
}
// recursive_size implementation
template<class T> requires (!std::ranges::range<T>)
constexpr auto recursive_size(const T& input)
{
return 1;
}
template<std::ranges::range Range> requires (!(std::ranges::input_range<std::ranges::range_value_t<Range>>))
constexpr auto recursive_size(const Range& input)
{
return std::ranges::size(input);
}
template<std::ranges::range Range> requires (std::ranges::input_range<std::ranges::range_value_t<Range>>)
constexpr auto recursive_size(const Range& input)
{
return std::transform_reduce(std::ranges::begin(input), std::end(input), std::size_t{}, std::plus<std::size_t>(), [](auto& element) {
return recursive_size(element);
});
}
// recursive_transform implementation
// recursive_invoke_result_t implementation
// from https://stackoverflow.com/a/65504127/6667035
// 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, std::copy_constructible 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, std::copy_constructible F, typename T>
using recursive_invoke_result_t = typename recursive_invoke_result<unwrap_level, F, T>::type;
template <std::ranges::range Range>
constexpr auto get_output_iterator(Range& output)
{
return std::inserter(output, std::ranges::end(output));
}
template<std::size_t dim, class T>
constexpr auto n_dim_vector_generator(T input, std::size_t times)
{
if constexpr (dim == 0)
{
return input;
}
else
{
auto element = n_dim_vector_generator<dim - 1>(input, times);
std::vector<decltype(element)> output(times, element);
return output;
}
}
template<std::size_t dim, std::size_t times, class T>
constexpr auto n_dim_array_generator(T input)
{
if constexpr (dim == 0)
{
return input;
}
else
{
auto element = n_dim_array_generator<dim - 1, times>(input);
std::array<decltype(element), times> output;
std::fill(std::begin(output), std::end(output), element);
return output;
}
}
template<std::size_t dim, class T>
constexpr auto n_dim_deque_generator(T input, std::size_t times)
{
if constexpr (dim == 0)
{
return input;
}
else
{
auto element = n_dim_deque_generator<dim - 1>(input, times);
std::deque<decltype(element)> output(times, element);
return output;
}
}
template<std::size_t dim, class T>
constexpr auto n_dim_list_generator(T input, std::size_t times)
{
if constexpr (dim == 0)
{
return input;
}
else
{
auto element = n_dim_list_generator<dim - 1>(input, times);
std::list<decltype(element)> output(times, element);
return output;
}
}
template<std::size_t dim, template<class...> class Container = std::vector, class T>
constexpr auto n_dim_container_generator(T input, std::size_t times)
{
if constexpr (dim == 0)
{
return input;
}
else
{
return Container(times, n_dim_container_generator<dim - 1, Container, T>(input, times));
}
}
void recursive_replace_copy_if_tests()
{
// std::vector<int> test case
std::vector<int> test_vector{ 5, 7, 4, 2, 8, 6, 1, 9, 0, 3 };
const int replaced_number = 55;
std::vector<int> ans_vector{ 5, 7, replaced_number, replaced_number, 8, 6, replaced_number, 9, replaced_number, replaced_number};
M_Assert(
recursive_replace_copy_if<1>(
test_vector,
std::bind(std::less<int>(), std::placeholders::_1, 5),
replaced_number) == ans_vector, "std::vector<int> test case failed.");
// std::vector<std::vector<int>> test case
std::vector<decltype(test_vector)> test_vector2 = {
test_vector, test_vector, test_vector
};
std::vector<decltype(ans_vector)> ans_vector2 = {
ans_vector, ans_vector, ans_vector
};
M_Assert(
recursive_replace_copy_if<2>(
test_vector2,
std::bind(std::less<int>(), std::placeholders::_1, 5),
replaced_number) == ans_vector2, "std::vector<std::vector<int>> test case failed.");
// std::vector<std::string> test case
std::vector<std::string> test_string_vector{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20" };
std::vector<std::string> ans_string_vector{ "0", "11", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20" };
M_Assert(
recursive_replace_copy_if<1>(
test_string_vector, [](std::string x) { return (x == "1"); }, "11"
) == ans_string_vector, "std::vector<std::string> test case failed."
);
// std::vector<std::vector<std::string>> test case
std::vector<decltype(test_string_vector)> test_string_vector2{ test_string_vector , test_string_vector , test_string_vector };
std::vector<decltype(ans_string_vector)> ans_string_vector2{ ans_string_vector, ans_string_vector, ans_string_vector };
M_Assert(
recursive_replace_copy_if<2>(
test_string_vector2, [](std::string x) { return (x == "1"); }, "11"
) == ans_string_vector2, "std::vector<std::vector<std::string>> test case failed."
);
// std::deque<int> test case
std::deque<int> test_deque;
test_deque.push_back(1);
test_deque.push_back(2);
test_deque.push_back(3);
test_deque.push_back(4);
test_deque.push_back(5);
test_deque.push_back(6);
std::deque<int> ans_deque;
ans_deque.push_back(1);
ans_deque.push_back(0);
ans_deque.push_back(3);
ans_deque.push_back(0);
ans_deque.push_back(5);
ans_deque.push_back(0);
M_Assert(
recursive_replace_copy_if<1>(test_deque, [](int x) { return (x % 2) == 0; }, 0)
== ans_deque, "std::deque<int> test case failed.");
// std::deque<std::deque<int>> test case
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);
std::deque<decltype(ans_deque)> ans_deque2;
ans_deque2.push_back(ans_deque);
ans_deque2.push_back(ans_deque);
ans_deque2.push_back(ans_deque);
M_Assert(recursive_replace_copy_if<2>(test_deque2, [](int x) { return (x % 2) == 0; }, 0)
== ans_deque2, "std::deque<std::deque<int>> test case failed.");
// std::list<int> test case
std::list<int> test_list = { 1, 2, 3, 4, 5, 6 };
std::list<int> ans_list = { 1, 0, 3, 0, 5, 0 };
M_Assert(recursive_replace_copy_if<1>(test_list, [](auto x) { return (x % 2) == 0; }, 0)
== ans_list, "std::list<int> test case failed.");
// std::list<std::list<int>> test case
std::list<decltype(test_list)> test_list2 = { test_list, test_list, test_list, test_list };
std::list<decltype(ans_list)> ans_list2 = { ans_list, ans_list, ans_list, ans_list };
M_Assert(recursive_replace_copy_if<2>(test_list2, [](auto x) { return (x % 2) == 0; }, 0)
== ans_list2, "std::list<std::list<int>> test case failed.");
std::cout << "All tests passed!\n";
return;
}
int main()
{
auto start = std::chrono::system_clock::now();
recursive_replace_copy_if_tests();
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
std::time_t end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
return EXIT_SUCCESS;
}
The output of the test code above:
All tests passed!
Computation finished at Thu Jul 11 07:59:26 2024
elapsed time: 0.000110102
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
A recursive_replace_copy_if Template Function Implementation in C++
What changes has been made in the code since last question?
I am trying to implement
recursive_replace_copy_iftemplate function with unwrap level in this post.Why a new review is being asked for?
Please review the implementation of
recursive_replace_copy_iftemplate function and its tests.