# Is a contiguous_range always a sized_range?

No.

`contiguous_range`

is:

```
template<class T>
concept contiguous_range =
ranges::random_access_range<T> &&
std::contiguous_iterator<ranges::iterator_t<T>> &&
requires(T& t) {
{ ranges::data(t) } ->
std::same_as<std::add_pointer_t<ranges::range_reference_t<T>>>;
};
```

and, as you can see, it `requires`

`random_access_range`

, which is:

```
template<class T>
concept random_access_range =
ranges::bidirectional_range<T> && std::random_access_iterator<ranges::iterator_t<T>>;
```

which, on the other hand, `requires`

`bidirectional_range`

, which is:

```
template<class T>
concept bidirectional_range =
ranges::forward_range<T> && std::bidirectional_iterator<ranges::iterator_t<T>>;
```

which `requires`

`forward_range`

, that is:

```
template<class T>
concept forward_range =
range::input_range<T> && std::forward_iterator<ranges::iterator_t<T>>;
```

and that `requires`

`input_range`

, so it needs:

```
template<class T>
concept input_range =
ranges::range<T> && std::input_iterator<ranges::iterator_t<T>>;
```

while `range`

only `requires`

that `std::ranges::begin()`

and `std::ranges::end()`

are valid for given `T`

.

You can play a similar game with those `std::XXX_iterator`

s. Nowhere there is anything for `std::ranges::size`

(which enables `sized_range`

).

No, not every `contiguous_range`

is a `sized_range`

.

The simplest example is a null-terminated string. It's contiguous, but we don't know its size in `O(1)`

time. And we can easily represent such a thing using sentinels:

```
struct ntbs_sentinel {
bool operator==(char const* p) const {
return *p == '\0';
}
};
struct ntbs {
char const* p;
char const* begin() const { return p; }
ntbs_sentinel end() const { return {}; }
};
static_assert(std::ranges::contiguous_range<ntbs>);
static_assert(!std::ranges::sized_range<ntbs>);
```

Another example would be, given some `std::string`

object `s`

and some predicate `p`

, either:

`s | std::views::take_while(p)`

`s | std::views::drop_while(p)`

The resulting range here is still contiguous, but we don't know where it ends (in the first case) or where it starts (in the second) so we don't know what its size is.

Being a `contiguous_range<T>`

is insufficient to be considered a `sized_range<T>`

, due to the presence of a sentinel. However, if you combine `contiguous_range<T>`

with `common_range<T>`

(which requires that the sentinel is an iterator), then `sized_range<T>`

must also be true.

Here's the logic. A `contiguous_range<T>`

is also a `random_access_range<T>`

. And a `random_access_range<T>`

means in part that `random_access_iterator<iterator_t<T>>`

is true. `common_range<T>`

means that `is_same<iterator_t<T>, sentinel_t<T>>`

. Therefore, `random_access_iterator<sentinel_t<T>>`

must also be true.

Now, `random_access_iterator<It>`

imposes a requirement that `std::sized_sentinel_for<I, I>`

is true. Since `iterator_t<T>`

and `sentinel_t<T>`

are the same type, this means that `std::sized_sentinel_for<sentinel_t<T>, iterator_t<T>>`

must also be true.

So, let's look at `sized_range<T>`

. This requires that `std::ranges::size(t)`

is valid for a `t`

of type `T`

.

`ranges::size<T>`

is valid if `T`

models `ranges::forward_range<T>`

(which it does) and `sentinel_t<T>`

and `iterator_t<T>`

models `std::sized_sentinel_for<sentinel_t<T>, iterator_t<T>>`

.

Which as previously stated, it does.