Что определяет место диагностики ошибки при ограничении шаблона концептом вместо SFINAE?
При использовании концепта проверка ограничения выполняется на этапе отбора шаблона до инстанцирования его тела. Если ограничение не выполнено, кандидат обычно становится непригодным, а ошибка появляется в месте вызова, когда подходящей перегрузки не осталось. В SFINAE похожий эффект достигается отказом подстановки в непосредственном контексте, но механизм менее явно выражает требования к типу.
Ошибка внутри уже выбранного тела шаблона не превращается в незаметный отказ кандидата: это обычная ошибка инстанцирования и она диагностируется как ошибка программы.
До появления concepts в C++20 ограничения шаблонов часто выражали через SFINAE, enable_if, специальные вспомогательные типы и void_t. Такой подход позволял исключать неподходящие перегрузки, но требовал кодировать требования косвенно и часто давал малопонятные сообщения компилятора.
Концепты появились как декларативный способ описывать допустимые свойства аргументов шаблона. Они отделяют проверку интерфейсных требований от реализации тела и позволяют компилятору сообщать, какое ограничение не выполнено.
Рассмотрим шаблон, который должен работать только с типами, поддерживающими определённую операцию. Если проверку оставить внутри тела, шаблон сначала будет выбран, а затем завершится глубокой ошибкой инстанцирования.
Если же требование задано ограничением, неподходящий шаблон исключается ещё при выборе кандидатов. Неверное размещение проверки приводит к ухудшению диагностики, неожиданному выбору перегрузки или к тому, что ошибка проявляется далеко от исходного вызова.
Ограничение концепта участвует в проверке пригодности шаблона. Компилятор проверяет выражение ограничения при подстановке аргументов и до инстанцирования тела функции. Невыполненное ограничение не является ошибкой внутри тела: функция просто не становится viable-кандидатом.
Минимальный пример:
Для типа без size() ограничение HasSize не выполняется. Если других подходящих перегрузок нет, компилятор сообщает, что вызов не разрешён и какое требование оказалось невыполненным. Тело length при этом не инстанцируется для неподходящего типа.
SFINAE действует только в контексте, где подстановка может завершиться отказом без ошибки: например, при формировании типа параметра или возвращаемого типа. Ошибка за пределами такого непосредственного контекста становится обычной ошибкой компиляции.
Концепты не делают любую ошибку в шаблоне мягкой. Если тип удовлетворяет ограничению, но реализация тела использует недоступную операцию, выбранная функция будет инстанцирована, и ошибка возникнет уже в её теле.
У концептов есть дополнительная семантика упорядочивания ограниченных перегрузок. Компилятор может определить, что один набор требований строже другого, через subsumption и нормализацию ограничений. Простое текстовое или логическое сходство выражений не всегда означает, что компилятор распознает отношение более строгого ограничения.
В библиотеке были две перегрузки: общая функция для любых типов и специализированная функция для типов с операцией size(). Изначально специальную перегрузку ограничили через сложный enable_if, из-за чего при ошибке пользователь видел длинное сообщение о несуществующих вложенных типах.
Рассматривались два варианта. Перенос проверки в тело функции был проще, но ухудшал диагностику и мог приводить к ошибке после выбора неправильной перегрузки. Сохранение SFINAE устраняло неподходящий кандидат, но оставляло сложное выражение ограничения и зависимость от непосредственного контекста подстановки.
Выбран был концепт с requires-выражением. Он исключал перегрузку до инстанцирования тела и показывал пользователю конкретное невыполненное требование. Результатом стала более предсказуемая перегрузка и существенно более локальная диагностика, однако библиотеке пришлось учитывать доступность C++20 и корректно формулировать атомарные ограничения для упорядочивания перегрузок.
Нет. Внешний результат может быть похожим: неподходящий кандидат исключается без немедленной фатальной ошибки. Но SFINAE основан на специальном правиле отказа подстановки в непосредственном контексте, а концепты используют проверку ограничений и правила пригодности кандидата. Это различие важно для диагностики, упорядочивания ограниченных перегрузок и поведения сложных выражений.
Функция уже прошла проверку ограничений и была выбрана. При инстанцировании тела недопустимая операция обычно вызывает обычную ошибку компиляции, а не исключает функцию по SFINAE. Поэтому концепт должен описывать все требования, которые являются частью публичного интерфейса шаблона, а не только наиболее очевидное свойство типа.
Для упорядочивания концептов компилятор анализирует нормализованные ограничения и их атомарные составляющие. Совпадение математического смысла или одинаковый результат проверки ещё не гарантирует, что два выражения будут распознаны как связанные отношением subsumption. Поэтому общие требования полезно выносить в именованные концепты и затем явно комбинировать их, например формируя более строгий концепт из базового и дополнительного требования.