3
\$\begingroup\$

This is a follow-up question for A recursive_count_if Function For Various Type Arbitrary Nested Iterable Implementation in C++ and A recursive_count_if Function with Specified value_type for Various Type Arbitrary Nested Iterable Implementation in C++. After digging into the thing that detecting the argument type of a function, I found that it is possible to simplify the T1 parameter in last implementation with boost::callable_traits::args_t syntax in Boost.CallableTraits library. As the result, recursive_count_if template function can be used exactly as the following code.

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

The implementation of recursive_count_if function with automatic type deducing:

#include <boost/callable_traits.hpp>
//  recursive_count_if implementation
template<class T1, class T2> requires (is_iterable<T1> && std::same_as<std::tuple<std::iter_value_t<T1>>, boost::callable_traits::args_t<T2>>)
auto recursive_count_if(const T1& input, const T2 predicate)
{
    return std::count_if(input.begin(), input.end(), predicate);
}

//  transform_reduce version
template<class T1, class T2> requires (is_iterable<T1> && !std::same_as<std::tuple<std::iter_value_t<T1>>, boost::callable_traits::args_t<T2>>)
auto recursive_count_if(const T1& input, const T2 predicate)
{
    return std::transform_reduce(std::begin(input), std::end(input), std::size_t{}, std::plus<std::size_t>(), [predicate](auto& element) {
        return recursive_count_if(element, predicate);
        });
}

The used is_iterable concept:

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

The constraints of usage

Because the type in the input lambda function plays the role of termination condition, you can not use auto keyword as generic lambdas here. If the lambda function like [](auto element) { } is passed in, compiling errors will pop up. If you want to use generic lambdas, maybe you can choose the previous version recursive_count_if function due to the termination condition is separated.

A Godbolt link is here.

All suggestions are welcome.

The summary information:

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Just curious, why recursive? \$\endgroup\$
    – bipll
    Nov 19, 2020 at 16:43
  • \$\begingroup\$ @bipll In order to make API more generic, the recursive technique used here to deal with arbitrary level nested iterables, including something like std::vector<std::vector<std::vector<std::string>>> or std::vector<std::vector<std::vector<std::vector<std::string>>>>... \$\endgroup\$
    – JimmyHu
    Nov 19, 2020 at 17:32

1 Answer 1

1
\$\begingroup\$

Don't try to deduce the predicate's parameter type

As Quuxplusone mentioned in the comments of the predecessor question, you should not try to determine the type of the predicate function's parameter, this is bound to fail. And indeed the problem immediately starts when you look at your predicate:

auto size5 = [](std::string s) { return s.size() == 5; };

And think: hey, that's making a copy of s, I should pass that by const reference instead:

auto size5 = [](const std::string &s) { return s.size() == 5; };

Now it fails the requires clauses of your functions. You might be able to add some more tricks to cast away cv-qualifiers and references before comparing the types with std::same_as, but that does not cover all possible situations either. For example, I could write a lambda which allows any type of std::basic_string to be checked:

auto size5 = []<typename T>(std::basic_string<T> s) { return s.size() == 5; };

As I suggested in the comments, you just want to write a concept that checks if the predicate can be applied to the argument it is fed, you don't need to check the types themselves. Here is a possible implementation:

template<typename Pred, typename T>
concept is_applicable_to_elements = requires(Pred predicate, const T &container)
{
    predicate(*container.begin());
};

template<class T1, class T2> requires is_applicable_to_elements<T2, T1>
auto recursive_count_if(const T1& input, const T2 predicate)
{
    return std::count_if(input.begin(), input.end(), predicate);
}

template<class T1, class T2>
auto recursive_count_if(const T1& input, const T2 predicate)
{
    return std::transform_reduce(std::begin(input), std::end(input), std::size_t{}, std::plus<std::size_t>(), [predicate](auto& element) {
        return recursive_count_if(element, predicate);
    });
}

Note that since all the templates used is_iterable(), you can remove that requirement, although if you keep it you get nicer error messages if you accidentily try to apply recursive_count_if() on something that does not support iterating over.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.