От чего зависит выбор между двумя шаблонными перегрузками, если обе подходят переданному типу, но одна ограничена более узким концептом?
Если ограничения одной перегрузки являются более строгими относительно другой, компилятор считает первую более специализированной и выбирает её. Это определяется не общим логическим рассуждением, а отношением subsumption: ограничения должны быть структурно связаны через одни и те же именованные концепты или совместимые атомарные ограничения.
Если компилятор не может установить такое отношение, вызов становится неоднозначным, даже когда человек видит логическое следствие одного ограничения из другого.
В C++ до появления концептов ограничения шаблонов часто выражали через SFINAE, std::enable_if и вспомогательные типы. Такой код был сложен для чтения, а ошибки выбора перегрузки возникали далеко от места, где шаблон объявлен.
Концепты, стандартизованные в C++20, сделали требования к типам частью интерфейса шаблона. Кроме проверки допустимости подстановки они добавили формализованный способ упорядочивать перегрузки по строгости ограничений.
Представим две перегрузки: первая принимает любой тип с методом size(), вторая — только тип с size() и empty(). Для контейнера с обоими методами подходят обе перегрузки.
Неверно считать, что компилятор всегда самостоятельно докажет любое логическое следствие. Если ограничения записаны как независимые выражения, их атомарные части могут не совпадать, поэтому выбор не более специализированной перегрузки приводит к ошибке неоднозначности.
При выборе перегрузки компилятор сначала проверяет, удовлетворяет ли тип ограничениям. Затем он сравнивает подходящие шаблоны. Ограничение считается более сильным, когда его нормализованная форма включает ограничение другой перегрузки и добавляет дополнительные требования.
Ключевое значение имеют атомарные ограничения и их идентичность. Именованный концепт сохраняет связь между ограничениями: если один концепт определён через другой, компилятор может использовать эту связь при упорядочивании.
Для std::vector<int> истинны оба концепта, но HasSizeAndEmpty явно построен на HasSize и добавляет требование empty(). Поэтому выбирается вторая перегрузка.
Практически это означает: общие требования следует выносить в именованный концепт, а специализированные — строить через него. Простое дублирование похожего выражения в двух объявлениях не гарантирует, что компилятор распознает отношение специализации.
Concept-based ordering применяется к перегрузкам функций и частичной упорядоченности шаблонов. Он не является механизмом динамического выбора: решение принимается на этапе компиляции. Если две перегрузки имеют независимые ограничения одинаковой применимости, вызов остаётся неоднозначным — нужно изменить концепты, добавить явный приоритет или убрать одну из перегрузок.
В библиотеке форматирования были две функции: общая — для объектов, у которых есть размер, и специализированная — для контейнеров, поддерживающих проверку пустоты. Разработчик написал два независимых requires-выражения, повторив проверку size() во второй функции. Для некоторых типов компилятор не мог доказать, что вторая перегрузка уже включает первую, и сборка завершалась неоднозначностью.
Вариант с enable_if позволил бы вручную задавать приоритет через дополнительные параметры шаблона, но сделал бы интерфейс менее читаемым и усложнил диагностику. Перегрузка с неименованными requires-выражениями была короче, но не выражала связь требований явно.
Выбранный вариант — базовый концепт HasSize и производный от него HasSizeAndEmpty. Он сделал намерение видимым, обеспечил корректное упорядочивание перегрузок и позволил получать более точные сообщения компилятора. В результате новые типы контейнеров автоматически попадали в нужную ветку без ручных тегов или приоритетов.
Нет. Для выбора более специализированной перегрузки важна не только математическая истинность импликации, но и структура нормализованных ограничений. Если два условия записаны разными атомарными выражениями, компилятор может не установить их связь.
Поэтому общий набор требований нужно оформлять отдельным концептом и использовать его в производном концепте. Это одновременно улучшает читаемость и делает отношение специализации доступным механизму subsumption.
Вызов станет неоднозначным, если ни один набор ограничений не считается более сильным. Например, концепты «имеет size()» и «имеет empty()» сами по себе не образуют иерархию: тип может удовлетворять обоим, но ни один концепт не включает другой.
Исправление зависит от дизайна API: можно создать составной концепт, явно задать приоритет через отдельную перегрузку или отказаться от перегрузки в пользу одной функции. Не следует рассчитывать на порядок объявлений — он не задаёт приоритет перегрузок.
Да, ограничения участвуют в частичной упорядоченности шаблонов класса. Более специализированное ограничение может выбрать частичную специализацию, если компилятор устанавливает subsumption между её требованиями и требованиями менее специализированной специализации.
Однако это не отменяет обычных правил частичной специализации: параметры шаблона и сами шаблонные формы также должны быть сопоставимы. Одних похожих requires недостаточно, если структуры специализаций не позволяют определить, какая из них более специализирована.