C++20 Concepts – Why Non-ranged STL Algorithms Aren’t Constrained with Concepts in C++20

c++c++20std-ranges

Take std::sort and std::ranges::sort as examples, the iterator class in std::ranges::sort is constrained with the concept std::random_access_iterator:

template< std::random_access_iterator I, std::sentinel_for<I> S,
          class Comp = ranges::less, class Proj = std::identity >
requires std::sortable<I, Comp, Proj>
constexpr I
    sort( I first, S last, Comp comp = {}, Proj proj = {} );

But std::sort is not:

template< class RandomIt >
void sort( RandomIt first, RandomIt last );

Why isn't std::sort (and all non-ranged algorithms) constrained?

A related question: What is the difference between std::fill_n and std::ranges::fill_n?

Best Answer

It is "constrained", just not in the concept sense:

[algorithm.requirements]p(4.7)

If an algorithm's template parameter is named RandomAccessIterator, RandomAccessIterator1, or RandomAccessIterator2, the template argument shall meet the Cpp17RandomAccessIterator requirements ([random.access.iterators]) if it is required to be a mutable iterator, or model random_access_iterator ([iterator.concept.random.access]) otherwise.

This "shall" requirement could be tested with a static_assert in your C++ standard library (and has always been able to). It's just a quality-of-implementation issue if the standard library implementors look for stuff that isn't actually used.

This is different from a concept constrained parameter, where the function does not participate in overload resolution.

template<typename T>
concept is_std_sortable = requires(T t) { std::sort(t, t); };
template<typename T>
concept is_std_ranges_sortable = requires(T t) { std::ranges::sort(t, t); };

static_assert(is_std_sortable<int>);
static_assert(!is_std_ranges_sortable<int>);

You can see how this would be necessary with the multiple overloads (range or iterator pair) and default arguments to select the correct function when called.
It isn't necessary for the non-ranges algorithms, which only have a single overload. The ones that have multiple overloads take an execution policy as the first argument and the second overload is essentially concept constrained ([algorithms.parallel.overloads]p4)

Related Question