Reputation: 1967
I want to write a function template that accepts ranges of MyType
-typed values, so I wrote
void f(const std::ranges::range auto& my_range) { /* ... */ }
Unfortunately this does not constrain the type of the values contained in the range. I could use a requires
clause before the function body block but I want something reusable and not specific to this function.
Naturally I wrote
template <class T, class V>
concept range_of = std::ranges::range<T> && std::is_same_v<V, std::ranges::range_value_t<T>>;
void f(const range_of<MyType> auto& my_range) { /* ... */ }
but I'm very surprised that this is not supported out-of-the-box by the standard library given how natural it seems. This also applies to std::ranges::view
for which I could write a similar view_of
concept.
Is my definition of range_of
incomplete or wrong? Or is there a good reason this isn't offered by the standard library?
Upvotes: 10
Views: 1908
Reputation: 302748
The problem here is: what is the actual constraint that you want?
Do you want to:
T
?T
?C
?Different contexts call for different versions of this. Your range_of
concept is a perfectly fine implementation of the first one of these (could use std::same_as
instead of std::is_same_v
but doesn't super matter). But what happens when you want something else? The specific choice of constraint is going to depend very much on what it is you want to do.
So the direct answer is: there's no range_of
concept because there's really no one true answer of what that concept should do.
A different answer would be that this isn't even what would be useful. What would be useful would be a direct way to pull out the associated types of a given range
to make it easy to add further constraints on them.
For instance, if we take Rust, we can define a function that takes an arbitrary iterator:
fn foo<I: Iterator>(it: I) { ... }
That's sort of akin to just void f(range auto)
. It's a constraint, but not a very useful one. But Iterator
, in Rust, has an associated type: its Item
. So if you wanted an Iterator
over a specific type, that's:
fn foo<I: Iterator<Item=i32>>(it: I) { ... }
If you wanted an Iterator
whose type satisfies some other constraint:
fn foo<T: Debug, I: Iterator<Item=T>>(it: I) { ... }
And it's really this ability - to pull out associated types and constrain on them directly - that we really lack.
Upvotes: 5