1
\$\begingroup\$

This is a follow-up question for A recursive_find_if_all Template Function Implementation in C++, A recursive_all_of Template Function Implementation in C++ and A recursive_all_of Template Function with Unwrap Level Implementation in C++. To support std::string, std::wstring, std::u8string and std::pmr::string(making recursive_find_if template function more generic), I am trying to implement recursive_find_if template function with unwrap level in this post.

The experimental implementation

  • recursive_find_if Template Function

    /*  recursive_find_if template function implementation with unwrap level
    */
    template<std::size_t unwrap_level, class T, class Proj = std::identity, class UnaryPredicate>
    requires(unwrap_level <= recursive_depth<T>())
    constexpr auto recursive_find_if(T&& value, UnaryPredicate&& p, Proj&& proj = {}) {
        if constexpr (unwrap_level > 0)
        {
            return std::ranges::find_if(value, [&](auto& element) {
                return recursive_find_if<unwrap_level - 1>(element, p, proj);
            }) != std::ranges::end(value);
        }
        else
        {
            return std::invoke(p, std::invoke(proj, value));
        }
    }
    

Full Testing Code

The full testing code:

//  A recursive_find_if Template Function with Unwrap Level Implementation in C++

#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <concepts>
#include <deque>
#include <execution>
#include <exception>
//#include <experimental/ranges/algorithm>
#include <experimental/array>
#include <functional>
#include <iostream>
#include <ranges>
#include <string>
#include <utility>
#include <vector>

//  is_reservable concept
template<class T>
concept is_reservable = requires(T input)
{
    input.reserve(1);
};

//  is_sized concept, https://codereview.stackexchange.com/a/283581/231235
template<class T>
concept is_sized = requires(T x)
{
    std::size(x);
};

template<typename T>
concept is_summable = requires(T x) { x + x; };

//  recursive_unwrap_type_t struct implementation
template<std::size_t, typename, typename...>
struct recursive_unwrap_type { };

template<class...Ts1, template<class...>class Container1, typename... Ts>
struct recursive_unwrap_type<1, Container1<Ts1...>, Ts...>
{
    using type = std::ranges::range_value_t<Container1<Ts1...>>;
};

template<std::size_t unwrap_level, class...Ts1, template<class...>class Container1, typename... Ts>
requires (  std::ranges::input_range<Container1<Ts1...>> &&
            requires { typename recursive_unwrap_type<
                                    unwrap_level - 1,
                                    std::ranges::range_value_t<Container1<Ts1...>>,
                                    std::ranges::range_value_t<Ts>...>::type; })                //  The rest arguments are ranges
struct recursive_unwrap_type<unwrap_level, Container1<Ts1...>, Ts...>
{
    using type = typename recursive_unwrap_type<
        unwrap_level - 1,
        std::ranges::range_value_t<Container1<Ts1...>>
        >::type;
};

template<std::size_t unwrap_level, typename T1, typename... Ts>
using recursive_unwrap_type_t = typename recursive_unwrap_type<unwrap_level, T1, Ts...>::type;

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

//  recursive_array_invoke_result implementation
template<std::size_t, typename, typename, typename...>
struct recursive_array_invoke_result { };

template<   typename F, 
            template<class, std::size_t> class Container,
            typename T,
            std::size_t N>
struct recursive_array_invoke_result<1, F, Container<T, N>>
{
    using type = Container<
        std::invoke_result_t<F, std::ranges::range_value_t<Container<T, N>>>,
        N>;
};

template<   std::size_t unwrap_level,
            typename F, 
            template<class, std::size_t> class Container,
            typename T,
            std::size_t N>
requires (  std::ranges::input_range<Container<T, N>> &&
            requires { typename recursive_array_invoke_result<
                                    unwrap_level - 1,
                                    F,
                                    std::ranges::range_value_t<Container<T, N>>>::type; })                //  The rest arguments are ranges
struct recursive_array_invoke_result<unwrap_level, F, Container<T, N>>
{
    using type = Container<
        typename recursive_array_invoke_result<
        unwrap_level - 1,
        F,
        std::ranges::range_value_t<Container<T, N>>
        >::type, N>;
};

template<   std::size_t unwrap_level,
            typename F,
            template<class, std::size_t> class Container,
            typename T,
            std::size_t N>
using recursive_array_invoke_result_t = typename recursive_array_invoke_result<unwrap_level, F, Container<T, N>>::type;

//  recursive_array_unwrap_type struct implementation, https://stackoverflow.com/a/76347485/6667035
template<std::size_t, typename>
struct recursive_array_unwrap_type { };

template<template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
struct recursive_array_unwrap_type<1, Container<T, N>>
{
    using type = std::ranges::range_value_t<Container<T, N>>;
};

template<std::size_t unwrap_level, template<class, std::size_t> class Container,
              typename T,
              std::size_t N>
requires (  std::ranges::input_range<Container<T, N>> &&
            requires { typename recursive_array_unwrap_type<
                                    unwrap_level - 1,
                                    std::ranges::range_value_t<Container<T, N>>>::type; })                //  The rest arguments are ranges
struct recursive_array_unwrap_type<unwrap_level, Container<T, N>>
{
    using type = typename recursive_array_unwrap_type<
        unwrap_level - 1,
        std::ranges::range_value_t<Container<T, N>>
        >::type;
};

template<std::size_t unwrap_level, class Container>
using recursive_array_unwrap_type_t = typename recursive_array_unwrap_type<unwrap_level, Container>::type;

//  https://codereview.stackexchange.com/a/253039/231235
template<std::size_t dim, class T, template<class...> class Container = std::vector>
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, T, Container>(input, times));
    }
}

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
    {
        std::array<decltype(n_dim_array_generator<dim - 1, times>(input)), times> output;
        for (size_t i = 0; i < times; i++)
        {
            output[i] = n_dim_array_generator<dim - 1, times>(input);
        }
        return output;
    }
}

//  recursive_depth function implementation
template<typename T>
constexpr std::size_t recursive_depth()
{
    return std::size_t{0};
}

template<std::ranges::input_range Range>
constexpr std::size_t recursive_depth()
{
    return recursive_depth<std::ranges::range_value_t<Range>>() + std::size_t{1};
}

//  recursive_depth function implementation with target type
template<typename T_Base, typename T>
constexpr std::size_t recursive_depth()
{
    return std::size_t{0};
}

template<typename T_Base, std::ranges::input_range Range>
requires (!std::same_as<Range, T_Base>)
constexpr std::size_t recursive_depth()
{
    return recursive_depth<T_Base, std::ranges::range_value_t<Range>>() + std::size_t{1};
}

/*  recursive_foreach_all template function performs specific function on input container exhaustively
    https://codereview.stackexchange.com/a/286525/231235
*/
namespace impl {

    template<class F, class Proj = std::identity>
    struct recursive_for_each_state {
        F f;
        Proj proj;
    };

    //  recursive_foreach_all template function implementation
    template<class T, class State>
    requires (recursive_depth<T>() == 0)
    constexpr void recursive_foreach_all(T& value, State& state) {
        std::invoke(state.f, std::invoke(state.proj, value));
    }

    template<class T, class State>
    requires (recursive_depth<T>() != 0)
    constexpr void recursive_foreach_all(T& inputRange, State& state) {
        for (auto& item: inputRange)
            impl::recursive_foreach_all(item, state);
    }

    //  recursive_reverse_foreach_all template function implementation
    template<class T, class State>
    requires (recursive_depth<T>() == 0)
    constexpr void recursive_reverse_foreach_all(T& value, State& state) {
        std::invoke(state.f, std::invoke(state.proj, value));
    }

    template<class T, class State>
    requires (recursive_depth<T>() != 0)
    constexpr void recursive_reverse_foreach_all(T& inputRange, State& state) {
        for (auto& item: inputRange | std::views::reverse)
            impl::recursive_reverse_foreach_all(item, state);
    }
}

template<class T, class Proj = std::identity, class F>
constexpr auto recursive_foreach_all(T& inputRange, F f, Proj proj = {})
{
    impl::recursive_for_each_state state(std::move(f), std::move(proj));
    impl::recursive_foreach_all(inputRange, state);
    return std::make_pair(inputRange.end(), std::move(state.f));
}

template<class T, class Proj = std::identity, class F>
constexpr auto recursive_reverse_foreach_all(T& inputRange, F f, Proj proj = {})
{
    impl::recursive_for_each_state state(std::move(f), std::move(proj));
    impl::recursive_reverse_foreach_all(inputRange, state);
    return std::make_pair(inputRange.end(), std::move(state.f));
}

template<class T, class I, class F>
constexpr auto recursive_fold_left_all(const T& inputRange, I init, F f)
{
    recursive_foreach_all(inputRange, [&](auto& value) {
        init = std::invoke(f, init, value);
    });

    return init;
}

template<class T, class I, class F>
constexpr auto recursive_fold_right_all(const T& inputRange, I init, F f)
{
    recursive_reverse_foreach_all(inputRange, [&](auto& value) {
        init = std::invoke(f, value, init);
    });

    return init;
}

//  recursive_count_if template function implementation
template<class T, std::invocable<T> Pred>
requires (recursive_depth<T>() == 0)
constexpr std::size_t recursive_count_if_all(const T& input, const Pred& predicate)
{
    return predicate(input) ? std::size_t{1} : std::size_t{0};
}

template<std::ranges::input_range Range, class Pred>
requires (recursive_depth<Range>() != 0)
constexpr auto recursive_count_if_all(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_all(element, predicate);
        });
}

template<std::size_t unwrap_level, class T, class Pred>
requires(unwrap_level <= recursive_depth<T>())
constexpr auto recursive_count_if(const T& input, const Pred& predicate)
{
    if constexpr (unwrap_level > 0)
    {
        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 predicate(input) ? 1 : 0;
    }
    
}

/*  recursive_all_of template function implementation with unwrap level
*/
template<std::size_t unwrap_level, class T, class Proj = std::identity, class UnaryPredicate>
requires(unwrap_level <= recursive_depth<T>())
constexpr auto recursive_all_of(T&& value, UnaryPredicate&& p, Proj&& proj = {}) {
    if constexpr (unwrap_level > 0)
    {
        return std::ranges::all_of(value, [&](auto&& element) {
            return recursive_all_of<unwrap_level - 1>(element, p, proj);
        });
    }
    else
    {
        return std::invoke(p, std::invoke(proj, value));
    }
}

/*  recursive_find_if template function implementation with unwrap level
*/
template<std::size_t unwrap_level, class T, class Proj = std::identity, class UnaryPredicate>
requires(unwrap_level <= recursive_depth<T>())
constexpr auto recursive_find_if(T&& value, UnaryPredicate&& p, Proj&& proj = {}) {
    if constexpr (unwrap_level > 0)
    {
        return std::ranges::find_if(value, [&](auto& element) {
            return recursive_find_if<unwrap_level - 1>(element, p, proj);
        }) != std::ranges::end(value);
    }
    else
    {
        return std::invoke(p, std::invoke(proj, value));
    }
}

//  recursive_any_of template function implementation with unwrap level
template<std::size_t unwrap_level, class T, class Proj = std::identity, class UnaryPredicate>
constexpr auto recursive_any_of(T&& value, UnaryPredicate&& p, Proj&& proj = {})
{
    return recursive_find_if<unwrap_level>(value, p, proj);
}

//  recursive_none_of template function implementation with unwrap level
template<std::size_t unwrap_level, class T, class Proj = std::identity, class UnaryPredicate>
constexpr auto recursive_none_of(T&& value, UnaryPredicate&& p, Proj&& proj = {})
{
    return !recursive_any_of<unwrap_level>(value, p, proj);
}

template<std::ranges::input_range T>
constexpr auto recursive_print(const T& input, const int level = 0)
{
    T output = input;
    std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
    std::transform(input.cbegin(), input.cend(), output.begin(), 
        [level](auto&& x)
        {
            std::cout << std::string(level, ' ') << x << std::endl;
            return x;
        }
    );
    return output;
}

template<std::ranges::input_range T>
requires (std::ranges::input_range<std::ranges::range_value_t<T>>)
constexpr T recursive_print(const T& input, const int level = 0)
{
    T output = input;
    std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
    std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
        [level](auto&& element)
        {
            return recursive_print(element, level + 1);
        }
    );
    return output;
}

void recursive_find_if_tests()
{
    auto test_vectors_1 = n_dim_container_generator<4, int, std::vector>(1, 3);
    test_vectors_1[0][0][0][0] = 2;
    assert(recursive_find_if<4>(test_vectors_1, [](auto&& i) { return i % 2 == 0; }));

    auto test_vectors_2 = n_dim_container_generator<4, int, std::vector>(3, 3);
    assert(recursive_find_if<4>(test_vectors_2, [](auto&& i) { return i % 2 == 0; }) == false);
    
    //  Tests with std::string
    auto test_vector_string = n_dim_container_generator<4, std::string, std::vector>("1", 3);
    assert(recursive_find_if<4>(test_vector_string, [](auto&& i) { return i == "1"; }));
    assert(recursive_find_if<4>(test_vector_string, [](auto&& i) { return i == "2"; }) == false);

    //  Tests with std::string, projection
    assert(recursive_find_if<4>(
        test_vector_string,
        [](auto&& i) { return i == "1"; },
        [](auto&& element) {return std::to_string(std::stoi(element) + 1); }) == false);
    assert(recursive_find_if<4>(
        test_vector_string,
        [](auto&& i) { return i == "2"; },
        [](auto&& element) {return std::to_string(std::stoi(element) + 1); }));
    
    //  Tests with std::array of std::string
    std::array<std::string, 3> word_array1 = {"foo", "foo", "foo"};
    assert(recursive_find_if<1>(word_array1, [](auto&& i) { return i == "foo"; }));
    assert(recursive_find_if<1>(word_array1, [](auto&& i) { return i == "bar"; }) == false);

    //  Tests with std::deque of std::string
    std::deque<std::string> word_deque1 = {"foo", "foo", "foo", "bar"};
    assert(recursive_find_if<1>(word_deque1, [](auto&& i) { return i == "foo"; }));
    assert(recursive_find_if<1>(word_deque1, [](auto&& i) { return i == "bar"; }));
    assert(recursive_find_if<1>(word_deque1, [](auto&& i) { return i == "abcd"; }) == false);
    assert(recursive_find_if<2>(word_deque1, [](auto&& i) { return i == 'a'; }));
    assert(recursive_find_if<2>(word_deque1, [](auto&& i) { return i == 'b'; }));
    assert(recursive_find_if<2>(word_deque1, [](auto&& i) { return i == 'c'; }) == false);

    std::vector<std::wstring> wstring_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        wstring_vector1.push_back(std::to_wstring(1));
    }
    assert(recursive_find_if<1>(wstring_vector1, [](auto&& i) { return i == std::to_wstring(1); }));
    assert(recursive_find_if<1>(wstring_vector1, [](auto&& i) { return i == std::to_wstring(2); }) == false);

    std::vector<std::u8string> u8string_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        u8string_vector1.push_back(u8"\u20AC2.00");
    }
    assert(recursive_find_if<1>(u8string_vector1, [](auto&& i) { return i == u8"\u20AC2.00"; }));
    assert(recursive_find_if<1>(u8string_vector1, [](auto&& i) { return i == u8"\u20AC1.00"; }) == false);

    std::pmr::string pmr_string1 = "123";
    std::vector<std::pmr::string> pmr_string_vector1 = {pmr_string1, pmr_string1, pmr_string1};
    assert(recursive_find_if<1>(pmr_string_vector1, [](auto&& i) { return i == "123"; }));
    assert(recursive_find_if<1>(pmr_string_vector1, [](auto&& i) { return i == "456"; }) == false);
    std::cout << "All tests passed!\n";

    return;
}

void recursive_any_of_tests()
{
    auto test_vectors_1 = n_dim_container_generator<4, int, std::vector>(1, 3);
    test_vectors_1[0][0][0][0] = 2;
    assert(recursive_any_of<4>(test_vectors_1, [](auto&& i) { return i % 2 == 0; }));

    auto test_vectors_2 = n_dim_container_generator<4, int, std::vector>(3, 3);
    assert(recursive_any_of<4>(test_vectors_2, [](auto&& i) { return i % 2 == 0; }) == false);
    
    //  Tests with std::string
    auto test_vector_string = n_dim_container_generator<4, std::string, std::vector>("1", 3);
    assert(recursive_any_of<4>(test_vector_string, [](auto&& i) { return i == "1"; }));
    assert(recursive_any_of<4>(test_vector_string, [](auto&& i) { return i == "2"; }) == false);

    //  Tests with std::string, projection
    assert(recursive_any_of<4>(
        test_vector_string,
        [](auto&& i) { return i == "1"; },
        [](auto&& element) {return std::to_string(std::stoi(element) + 1); }) == false);
    assert(recursive_any_of<4>(
        test_vector_string,
        [](auto&& i) { return i == "2"; },
        [](auto&& element) {return std::to_string(std::stoi(element) + 1); }));
    
    //  Tests with std::array of std::string
    std::array<std::string, 3> word_array1 = {"foo", "foo", "foo"};
    assert(recursive_any_of<1>(word_array1, [](auto&& i) { return i == "foo"; }));
    assert(recursive_any_of<1>(word_array1, [](auto&& i) { return i == "bar"; }) == false);

    //  Tests with std::deque of std::string
    std::deque<std::string> word_deque1 = {"foo", "foo", "foo", "bar"};
    assert(recursive_any_of<1>(word_deque1, [](auto&& i) { return i == "foo"; }));
    assert(recursive_any_of<1>(word_deque1, [](auto&& i) { return i == "bar"; }));
    assert(recursive_any_of<1>(word_deque1, [](auto&& i) { return i == "abcd"; }) == false);
    assert(recursive_any_of<2>(word_deque1, [](auto&& i) { return i == 'a'; }));
    assert(recursive_any_of<2>(word_deque1, [](auto&& i) { return i == 'b'; }));
    assert(recursive_any_of<2>(word_deque1, [](auto&& i) { return i == 'c'; }) == false);

    std::vector<std::wstring> wstring_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        wstring_vector1.push_back(std::to_wstring(1));
    }
    assert(recursive_any_of<1>(wstring_vector1, [](auto&& i) { return i == std::to_wstring(1); }));
    assert(recursive_any_of<1>(wstring_vector1, [](auto&& i) { return i == std::to_wstring(2); }) == false);

    std::vector<std::u8string> u8string_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        u8string_vector1.push_back(u8"\u20AC2.00");
    }
    assert(recursive_any_of<1>(u8string_vector1, [](auto&& i) { return i == u8"\u20AC2.00"; }));
    assert(recursive_any_of<1>(u8string_vector1, [](auto&& i) { return i == u8"\u20AC1.00"; }) == false);

    std::pmr::string pmr_string1 = "123";
    std::vector<std::pmr::string> pmr_string_vector1 = {pmr_string1, pmr_string1, pmr_string1};
    assert(recursive_any_of<1>(pmr_string_vector1, [](auto&& i) { return i == "123"; }));
    assert(recursive_any_of<1>(pmr_string_vector1, [](auto&& i) { return i == "456"; }) == false);
    std::cout << "All tests passed!\n";

    return;
}

void recursive_none_of_tests()
{
    auto test_vectors_1 = n_dim_container_generator<4, int, std::vector>(1, 3);
    test_vectors_1[0][0][0][0] = 2;
    assert(recursive_none_of<4>(test_vectors_1, [](auto&& i) { return i % 2 == 0; }) == false);

    auto test_vectors_2 = n_dim_container_generator<4, int, std::vector>(3, 3);
    assert(recursive_none_of<4>(test_vectors_2, [](auto&& i) { return i % 2 == 0; }));
    
    //  Tests with std::string
    auto test_vector_string = n_dim_container_generator<4, std::string, std::vector>("1", 3);
    assert(recursive_none_of<4>(test_vector_string, [](auto&& i) { return i == "1"; }) == false);
    assert(recursive_none_of<4>(test_vector_string, [](auto&& i) { return i == "2"; }));

    //  Tests with std::string, projection
    assert(recursive_none_of<4>(
        test_vector_string,
        [](auto&& i) { return i == "1"; },
        [](auto&& element) {return std::to_string(std::stoi(element) + 1); }));
    assert(recursive_none_of<4>(
        test_vector_string,
        [](auto&& i) { return i == "2"; },
        [](auto&& element) {return std::to_string(std::stoi(element) + 1); }) == false);
    
    //  Tests with std::array of std::string
    std::array<std::string, 3> word_array1 = {"foo", "foo", "foo"};
    assert(recursive_none_of<1>(word_array1, [](auto&& i) { return i == "foo"; }) == false);
    assert(recursive_none_of<1>(word_array1, [](auto&& i) { return i == "bar"; }));

    //  Tests with std::deque of std::string
    std::deque<std::string> word_deque1 = {"foo", "foo", "foo", "bar"};
    assert(recursive_none_of<1>(word_deque1, [](auto&& i) { return i == "foo"; }) == false);
    assert(recursive_none_of<1>(word_deque1, [](auto&& i) { return i == "bar"; }) == false);
    assert(recursive_none_of<1>(word_deque1, [](auto&& i) { return i == "abcd"; }));
    assert(recursive_none_of<2>(word_deque1, [](auto&& i) { return i == 'a'; }) == false);
    assert(recursive_none_of<2>(word_deque1, [](auto&& i) { return i == 'b'; }) == false);
    assert(recursive_none_of<2>(word_deque1, [](auto&& i) { return i == 'c'; }));

    std::vector<std::wstring> wstring_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        wstring_vector1.push_back(std::to_wstring(1));
    }
    assert(recursive_none_of<1>(wstring_vector1, [](auto&& i) { return i == std::to_wstring(1); }) == false);
    assert(recursive_none_of<1>(wstring_vector1, [](auto&& i) { return i == std::to_wstring(2); }));

    std::vector<std::u8string> u8string_vector1{};
    for(int i = 0; i < 4; ++i)
    {
        u8string_vector1.push_back(u8"\u20AC2.00");
    }
    assert(recursive_none_of<1>(u8string_vector1, [](auto&& i) { return i == u8"\u20AC2.00"; }) == false);
    assert(recursive_none_of<1>(u8string_vector1, [](auto&& i) { return i == u8"\u20AC1.00"; }));

    std::pmr::string pmr_string1 = "123";
    std::vector<std::pmr::string> pmr_string_vector1 = {pmr_string1, pmr_string1, pmr_string1};
    assert(recursive_none_of<1>(pmr_string_vector1, [](auto&& i) { return i == "123"; }) == false);
    assert(recursive_none_of<1>(pmr_string_vector1, [](auto&& i) { return i == "456"; }));
    std::cout << "All tests passed!\n";

    return;
}

int main()
{
    auto start = std::chrono::system_clock::now();
    recursive_find_if_tests();
    recursive_any_of_tests();
    recursive_none_of_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!
All tests passed!
All tests passed!
Computation finished at Mon Feb  5 06:35:28 2024
elapsed time: 0.00237049

Godbolt link is here.

All suggestions are welcome.

The summary information:

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

This looks fine, except that you indeed did not constrain Proj and UnaryPredicate, which will result in hard to read error messages when you pass a projection and/or unary predicate of the wrong type.

Let's look at how std::ranges::find_if() constrains them:

template<ranges::input_range R, class Proj = std::identity,
         std::indirect_unary_predicate<std::projected<ranges::iterator_t<R>, Proj>> Pred>
constexpr ranges::borrowed_iterator_t<R>
find_if(R&& r, Pred pred, Proj proj = {});

So it contrains the input range and the predicate. It doesn't constrain the projection directly, but any errors there will be caught by the constraint on the predicate. Let's start with the constraint on the input range. In your case, you want T to be a nested range of at least unwrap_levels deep. Unfortunately, you can't use if constexpr inside a concept definition, and you also can't declare recursive concepts. But the trick is to create a type trait using recursive structs, or even better, a recursive constexpr function:

template<std::size_t unwrap_level, typename T>
static constexpr bool is_recursive_input_range() {
    if constexpr (unwrap_level == 0) {
        return true;
    } else if constexpr (std::ranges::input_range<T>) {
        return is_recursive_input_range<unwrap_level - 1,
                   std::ranges::range_value_t<T>>();
    } else {
        return false;
    }
}

And then we can create a concept based on that:

template<typename T, std::size_t unwrap_level>
concept recursive_input_range =
    is_recursive_input_range<unwrap_level, T>();

This allows you to write:

template<std::size_t unwrap_level, recursive_input_range<unwrap_level> T, …>
constexpr auto recursive_find_if(T&& value, …) {
    …
}

That should give you an idea of how to create recursive concepts. It unfortunately requires some puzzle solving skills. But now you should be able to create a constraint for Pred. At first glance, you don't even need to create a new concept, you only need to create a recursive version of std::ranges::iterator_t, so that you can write:

template<
    std::size_t unwrap_level,
    recursive_input_range<unwrap_level> T,
    typename Proj = std::identity,
    std::indirect_unary_predicate<
        std::projected<recursive_iterator_t<unwrap_level, T>, Proj>
    > Pred
>
constexpr auto recursive_find_if(T&& value, …) {
    …
}

However, this doesn't work if you unwrap it all the way to the non-range type. Maybe there are other type traits and concepts in the standard library that would help you create a working version, but if not, create your own concept, maybe so you can write it like:

template<
    std::size_t unwrap_level,
    recursive_input_range<unwrap_level> T,
    typename Proj = std::identity,
    recursive_projected_predicate<unwrap_level, T, Proj> Pred
>
constexpr auto recursive_find_if(T&& value, …) {
    …
}

I'll leave that as an exercise for the reader.

\$\endgroup\$
1
  • \$\begingroup\$ Besides your recursive_input_range, I am thinking about recursive_invocable concept and it takes unwrap_level, F and Ranges... \$\endgroup\$
    – JimmyHu
    Feb 6 at 12:25
0
\$\begingroup\$

Constrain unwrap_level to recursive_any_of and recursive_none_of template function

requires(unwrap_level <= recursive_depth<T>()) clause already applied to recursive_find_if function, this constrain can also be applied to recursive_any_of and recursive_none_of template function:

//  recursive_any_of template function implementation with unwrap level
template<std::size_t unwrap_level, class T, class Proj = std::identity, class UnaryPredicate>
requires(unwrap_level <= recursive_depth<T>())
constexpr auto recursive_any_of(T&& value, UnaryPredicate&& p, Proj&& proj = {})
{
    return recursive_find_if<unwrap_level>(value, p, proj);
}

//  recursive_none_of template function implementation with unwrap level
template<std::size_t unwrap_level, class T, class Proj = std::identity, class UnaryPredicate>
requires(unwrap_level <= recursive_depth<T>())
constexpr auto recursive_none_of(T&& value, UnaryPredicate&& p, Proj&& proj = {})
{
    return !recursive_any_of<unwrap_level>(value, p, proj);
}
\$\endgroup\$

Your Answer

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

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