Почему алгоритмы STL без ранжирования не ограничены концепциями C++20?

Возьмите std::sort и std::ranges::sort в качестве примера, класс итератора в std::ranges::sort ограничен концепцией 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 = {} );

Но std::sort это не:

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

Почему std::sort (и все алгоритмы без ранжирования) не ограничены?

Связанный с этим вопрос: В чем разница между std::fill_n и std::ranges::fill_n?

Потому что std::sort предшествует понятиям

bolov 27.07.2024 05:34

ИМХО: C++ — это плата за то, что вы используете язык. Если вам нужны ограничения типов и концепции, платите за это, используя диапазоны. Если нет, продолжайте использовать <algorithm> и не платите за дополнительное время компиляции, выполняющее ограничения.

NathanOliver 27.07.2024 05:34

C++20 представил как std::ranges, так и концепции, поэтому дал очевидную возможность/мотивацию для проектирования/спецификации std::ranges для использования концепций. Старые алгоритмы, которые принимают пары итераторов (например, std::sort()), появились раньше концепций и просто не обновлялись в C++20. Это типично для того, как развивается стандарт C++ (и большинство стандартов с формальным процессом управления) — требуется время/усилия для обновления старых функций для использования новых функций, и это увеличивает риск поломки чего-либо. Будущие предложения (в зависимости от времени/усилий) могут изменить или не изменить это.

Peter 27.07.2024 06:37

@NathanOliver, но была ли когда-либо «плата за то, что вы используете» применима ко времени компиляции? Если ограниченный std::sort для допустимых входных данных будет создавать тот же двоичный код, что и нынешняя версия без ограничений, почему я должен беспокоиться об увеличении времени компиляции? Возможно, причиной отсутствия ограничений старых алгоритмов является обратная совместимость, и в этом случае, я полагаю, некоторые ограничения могут сломаться во время компиляции кода, который был допустим в старых стандартах, а некоторые могут сломать код, который изначально был недопустим, что и произошло. работать только из-за УБ.

Enlico 27.07.2024 11:16

Я полагаю, что в принципе можно добавить ограничения, которые могут привести к поломке последнего типа.

Enlico 27.07.2024 11:16
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
5
156
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Конечно, есть практические и исторические причины; но если кто-то хочет подумать о причине постфактум:

В противном случае вы сломаете много кода; Требования к стилю диапазона более ограничены, чем вы можете себе представить. Существует много кода (пользовательских итераторов), который почти случайно работает с алгоритмами и определенно не компилируется с std::ranges алгоритмами.

Возьмем, к примеру, общее требование в std::ranges, такое как indirectly_writablehttps://en.cppreference.com/w/cpp/iterator/indirectly_writable. Могу поспорить, что почти все нетривиальные итераторы, написанные до Concepts, на самом деле не будут соответствовать всем наложенным ограничениям.

Интересно, действительно ли многие из случаев «почти случайно сработавших» на самом деле являются UB?

Enlico 27.07.2024 11:19

Для сравнения диапазонов требуется «весь» набор сравнения, тогда как старые алгоритмы требуют только < и/или ==. Использование полных концепций приведет к поломке кода, а предоставление неполных концепций кажется плохим.

Jarod42 27.07.2024 13:38

@Enlico, это не UB в обычном смысле времени выполнения. Это на более смысловом уровне. Как сказал Джарод, предположим, что тип определяет op< и op==. Для классического STL этого достаточно, чтобы тип был заказан. вы даже можете оставить неопределенными op> и op!= или даже определить их независимым (непоследовательным) способом. STL claasic не заметит. Однако диапазоны STL могут жаловаться на противоречивые или неопределенные операции, даже если вы знаете, что реализация их не использует!

alfC 27.07.2024 20:31

@alfC, поэтому я сказал «многие», а не все. Случай, о котором вы говорите, был законным кодом, и он остается таковым, потому что старые алгоритмы не были ограничены. Если бы старые алгоритмы были ограничены, этот код сломался бы во время компиляции. Интересно, существует ли закат ограничений, которые, если их добавить, сломают только (во время компиляции и/или времени выполнения?) код, который изначально демонстрировал UB. Может быть, я просто говорю чушь.

Enlico 27.07.2024 20:44

Нет, я думаю, что ваше беспокойство обосновано. и да, может быть UB, но это больше похоже на «библиотечный» UB в том смысле, что пользовательские реализации итераторов могут работать с некоторыми реализациями STL, а не с другими, или что небольшие изменения в реализации могут привести к тому, что некоторые вещи не компилируются, учитывая разные или неправильные результаты или, что маловероятно, но возможно, UB, как вы сказали. UB всегда возможен, я говорю о том, что это не является чем-то особенным, связанным с наложением ограничений.

alfC 27.07.2024 20:56
Ответ принят как подходящий

Это «ограничено», но не в концептуальном смысле:

[алгоритм.требования]p(4.7)

Если параметр шаблона алгоритма имеет имя RandomAccessIterator, RandomAccessIterator1 или RandomAccessIterator2, аргумент шаблона должен соответствовать требованиям Cpp17RandomAccessIterator ([random.access.iterators] ), если он должен быть изменяемым итератором или моделью random_access_iterator ( [iterator.concept.random.access]) иначе.

Это требование «должно» можно проверить с помощью static_assert в вашей стандартной библиотеке C++ (и это всегда было возможно). Это просто проблема качества реализации, если разработчики стандартной библиотеки ищут вещи, которые на самом деле не используются.

Это отличается от концептуально ограниченного параметра, где функция не участвует в разрешении перегрузки.

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

Вы можете увидеть, насколько это необходимо, с помощью нескольких перегрузок (диапазон или пара итераторов) и аргументов по умолчанию для выбора правильной функции при вызове.
В этом нет необходимости для алгоритмов без диапазонов, которые имеют только одну перегрузку. Те, которые имеют несколько перегрузок, принимают политику выполнения в качестве первого аргумента, а вторая перегрузка по сути ограничена концепцией ([algorithms.parallel.overloads]p4).

Может быть, глупый вопрос: зачем это нужно при многократных перегрузках? Что делать, необходимо?

Enlico 27.07.2024 16:20

@Enlico Если бы не было ограничений, то std::ranges::sort(vec.begin(), vec.end()) было бы неоднозначно: перегрузка пары итераторов или перегрузка диапазона, где вторым аргументом был компаратор. Аналогичная проблема возникла с конструктором пары итераторов std::string, конкурирующим с перегрузкой size_type, CharT (№10 здесь: en.cppreference.com/w/cpp/string/basic_string/basic_string).

Artyer 27.07.2024 19:08

Думаю, я понял исходную мысль, но не совсем уловил аналогию, потому что те две перегрузки, о которых вы упоминаете std::string, не являются двусмысленными, не так ли?

Enlico 27.07.2024 20:37

Это правильно, но этот ответ больше касается внутренней механики std::range, чем того, почему алгоритмы без диапазонов не ограничены требованиями.

alfC 27.07.2024 20:37

@Энлико char count = 12; std::string(count, 'c'); // Would have called the two iterator overload. Или int count = 12; int char = 'c'; std::string(count, char);

Artyer 27.07.2024 20:49

@alfC Три момента: алгоритмы без диапазонов имеют «ограничения», алгоритмы без диапазонов не нуждаются в ограничениях, таких как std::ranges::, в ситуациях, когда алгоритмам без диапазонов потребуются ограничения, они в основном нужны

Artyer 27.07.2024 20:51

@Артьер, я до сих пор не понимаю, что ты имеешь в виду. char count = 12; std::string(count, 'c'); вызывает size_type, CharT ctor, так что же вы имеете в виду, когда говорите, что можно было бы назвать перегрузку двух итераторов? В таком случае? Случай, когда size_type было просто именем неограниченного параметра шаблона?

Enlico 27.07.2024 21:16

@Enlico Если конструктор пары итераторов не был ограничен: godbolt.org/z/xzr1YEe47

Artyer 28.07.2024 09:38

Другие вопросы по теме

Похожие вопросы

Могу ли я написать библиотеку для предварительной загрузки на C++? Есть ли что-нибудь, что мне нужно сделать, кроме добавления `extern "C"` к функциям для перехвата?
Почему clang думает, что у меня есть конструктор копирования?
Почему типы выходят из пространства имен при включении после заголовка вектора?
Инициализация вектора с помощью класса, который имеет параметр конструктора передачи по ссылке и сохраняет эту ссылку как член
Как я могу использовать VSCode с CMake и иметь разные цели, каждая из которых имеет разную архитектуру и набор инструментов?
Неточные вычисления последовательности Фибоначчи во время компиляции в рекурсивной лямбда-выражении
Синхронизируйте три потока в C++ один за другим
Явные удаленные конструкторы – имеет ли это значение?
Сопрограмма, ожидаемая в списке параметров, нарушает другой параметр?
Std::variant::operator< неожиданный вызов неявного преобразования bool. Зависит от стандартов