Skip to main content
deleted 78 characters in body
Source Link
G. Sliepen
  • 69.5k
  • 3
  • 75
  • 180

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:

// Primary template
template<std::size_t unwrap_level, typename T>
structstatic recursive_input_range_trait:constexpr std::false_typebool is_recursive_input_range() {};

// Terminating case
template<class T>
struct recursive_input_range_trait<0,if T>:constexpr std::true_type(unwrap_level == 0) {};

// Recursing case      return true;
template<std::size_t unwrap_level,   } else if constexpr (std::ranges::input_rangeinput_range<T>) T>{
struct recursive_input_range_trait<unwrap_level, T>:
    recursive_input_range_trait<unwrap_level  return is_recursive_input_range<unwrap_level - 1,
                   std::ranges::range_value_t<T>>();
    } else {
        return false;
   std::range::range_value_t<T>> {};
}
template<std::size_ttemplate<typename unwrap_levelT, typenamestd::size_t T>unwrap_level>
concept recursive_input_range =
    recursive_input_range_trait<unwrap_levelis_recursive_input_range<unwrap_level, T>::value;();

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:

// Primary template
template<std::size_t unwrap_level, typename T>
struct recursive_input_range_trait: std::false_type {};

// Terminating case
template<class T>
struct recursive_input_range_trait<0, T>: std::true_type {};

// Recursing case
template<std::size_t unwrap_level, std::ranges::input_range T>
struct recursive_input_range_trait<unwrap_level, T>:
    recursive_input_range_trait<unwrap_level - 1,
                                std::range::range_value_t<T>> {};
template<std::size_t unwrap_level, typename T>
concept recursive_input_range =
    recursive_input_range_trait<unwrap_level, T>::value;

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;
    }
}
template<typename T, std::size_t unwrap_level>
concept recursive_input_range =
    is_recursive_input_range<unwrap_level, T>();
Source Link
G. Sliepen
  • 69.5k
  • 3
  • 75
  • 180

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:

// Primary template
template<std::size_t unwrap_level, typename T>
struct recursive_input_range_trait: std::false_type {};

// Terminating case
template<class T>
struct recursive_input_range_trait<0, T>: std::true_type {};

// Recursing case
template<std::size_t unwrap_level, std::ranges::input_range T>
struct recursive_input_range_trait<unwrap_level, T>:
    recursive_input_range_trait<unwrap_level - 1,
                                std::range::range_value_t<T>> {};

And then we can create a concept based on that:

template<std::size_t unwrap_level, typename T>
concept recursive_input_range =
    recursive_input_range_trait<unwrap_level, T>::value;

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.