recursion – A recursive_count_if Function with Unwrap Level for Various Type Arbitrary Nested Iterable Implementation in C++

This is a follow-up question for A recursive_count_if Function For Various Type Arbitrary Nested Iterable Implementation in C++, A recursive_count_if Function with Specified value_type for Various Type Arbitrary Nested Iterable Implementation in C++ and A recursive_count_if Function with Automatic Type Deducing from Lambda for Various Type Arbitrary Nested Iterable Implementation in C++. As G. Sliepen’s answer Don't try to deduce the predicate's parameter type and Quuxplusone’s answer mentioned I think the user of this API should have to tell the function explicitly how many "levels" to unwrap downward, I am trying to implement another version recursive_count_if template function with unwrap level parameter. Thanks to Quuxplusone for providing the hint of using if constexpr to achieve this.

template<std::size_t unwrap_level, class T1, class T2> requires (is_iterable<T1>)
auto recursive_count_if(const T1& input, const T2 predicate)
{
    if constexpr (unwrap_level > 1)
    {
        return std::transform_reduce(std::cbegin(input), std::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(input.begin(), input.end(), predicate);
    }
}

The used is_iterable concept is as below. I keep it in order to get nicer error messages if user accidentily try to apply this recursive_count_if() on something that can not be iterated over.

template<typename T>
concept is_iterable = requires(T x)
{
    *std::begin(x);
    std::end(x);
};

Some test cases are as below. With this version recursive_count_if, you can always use auto keyword in the input lambda parameter.

//  std::vector<std::vector<int>> case
std::vector<int> test_vector{ 1, 2, 3, 4, 4, 3, 7, 8, 9, 10 };
std::vector<decltype(test_vector)> test_vector2;
test_vector2.push_back(test_vector);
test_vector2.push_back(test_vector);
test_vector2.push_back(test_vector);

// use a lambda expression to count elements divisible by 3.
int num_items1 = recursive_count_if<2>(test_vector2, ()(auto& i) {return i % 3 == 0; });
std::cout << "#number divisible by three: " << num_items1 << 'n';

// std::deque<std::deque<int>> case
std::deque<int> test_deque;
test_deque.push_back(1);
test_deque.push_back(2);
test_deque.push_back(3);

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

// use a lambda expression to count elements divisible by 3.
int num_items2 = recursive_count_if<2>(test_deque2, ()(auto& i) {return i % 3 == 0; });
std::cout << "#number divisible by three: " << num_items2 << 'n';

std::vector<std::vector<std::string>> v = { {"hello"}, {"world"} };
auto size5 = ()(auto& s) { return s.size() == 5; };
auto n = recursive_count_if<2>(v, size5);
std::cout << "n:" << n << std::endl;

//  std::vector<std::vector<std::vector<int>>> case
std::vector<decltype(test_vector2)> test_vector3;
test_vector3.push_back(test_vector2);
test_vector3.push_back(test_vector2);
test_vector3.push_back(test_vector2);
std::cout << recursive_count_if<1>(test_vector3,
    (test_vector2)(auto& element)
    {
        return std::equal(element.begin(), element.end(), test_vector2.begin());
    }) << std::endl;

A Godbolt link is here.

All suggestions are welcome.

The summary information: