Use std::invocable
It turns out there is a standard concept to check whether a function can be applied to a (set of) argument(s): std::invocable. It's very handy combined with the following:
Avoid having to deal with ranges of ranges
I think you can avoid having to deal with "ranges of ranges", by simplifying the templates like so:
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>) // see below constexpr auto recursive_count_if(const Range& input, const Pred& predicate) { 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(element, predicate); }); } Fixing control over recursion
As pointed out by others, one big issue with these recursive functions is that there are classes that can be both seen as values or as containers themselves. For example, if we have a std::vector<std::string>, do we want to count the strings or the characters in the strings? Since you referenced this earlier question, I assume you want to have the predicate control when to end recursion. Let's add a testcase for it:
std::vector<std::string> vector_of_strings{ "Hello", "world!" }; std::cout << "Number of non-empty strings: " << recursive_count_if(vector_of_strings, [](const std::string &s) {return !s.empty();}) << '\n'; std::cout << "Number of lower-case characters: " << recursive_count_if(vector_of_strings, [](char c) {return std::islower(static_cast<unsigned char>(c));}) << '\n'; std::cout << "Number of things: " << recursive_count_if(vector_of_strings, [](const T &i) {(void)i; return true;}) << '\n'; The expected output is:
Number of non-empty strings: 2 Number of lower-case characters: 9 Number of things: 11 // did you expect this? Your code will fail the case of counting strings, as it cannot decide what template is more important. There are two possible fixes; the first is to not restrict the recursive template overload:
template<std::ranges::input_range Range, class Pred> constexpr auto recursive_count_if(const Range& input, const Pred& predicate) { 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(element, predicate); }); } Since that template is now unambiguously more generic than the first one. The drawback is a more confusing error message in case you try to recursive count a container using a lambda that doesn't match any recursion level. Alternatively, restrict it even more:
template<std::ranges::input_range Range, class Pred> requires (std::ranges::input_range<std::ranges::range_value_t<Range>> && !is_applicable_to_elements(Range, Pred)) constexpr auto recursive_count_if(const Range& input, const Pred& predicate) { 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(element, predicate); }); } Slightly better error messages. I also had to do that with my version that avoids ranges of ranges. A third alternative is to not add any requirement that the input is a range, for example:
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<class Range, class Pred> constexpr auto recursive_count_if(const Range& input, const Pred& predicate) { 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(element, predicate); }); } However this gives very long error messages. Your version with the extra requirements gives the nicest error messages in case you do something weird like:
std::vector<int> test_vector{1, 2, 3, 4, 4, 3, 7, 8, 9, 10}; std::cout << "Number of non-empty strings: " << recursive_count_if(test_vector, [](std::string i) {return !i.empty(); }) << '\n';