Разбор последствий: почему без зависимого от параметра условия static_assert(false) может сделать шаблон непригодным даже при выборе другой ветки?
static_assert(false) является независимым от параметра шаблона выражением, поэтому его ложность может быть обнаружена уже при определении шаблона. В результате шаблон становится некорректным, даже если при конкретной инстанциации ветка с этим утверждением должна быть отброшена. Чтобы проверка выполнялась только для неподдерживаемых специализаций, условие делают зависимым от параметра шаблона.
До появления if constexpr шаблонный код часто разделяли частичными специализациями или перегрузками. Это позволяло выбирать реализацию для разных типов, но усложняло код и ухудшало локальность диагностики.
if constexpr дал возможность описывать выбор реализации внутри одной функции. Однако отброшенная ветка не избавляет от проверки выражений, которые уже заведомо некорректны и не зависят от параметров шаблона. Для таких случаев появился идиоматический приём с зависимым ложным условием.
Предположим, функция поддерживает несколько категорий типов, а для остальных должна завершаться понятной ошибкой компиляции. Если безусловно написать static_assert(false), компилятор может отвергнуть сам шаблон до того, как будет известно, какая ветка потребуется для конкретного типа.
Неверное решение приводит к двум проблемам: поддерживаемые типы тоже перестают компилироваться, а диагностика не связывается с фактическим использованием неподдерживаемого типа. Слишком слабая проверка, напротив, может позволить неподдерживаемой специализации пройти дальше и вызвать менее понятную ошибку в другом месте.
Условие для static_assert делают зависимым от параметра шаблона, сохраняя при этом значение false для любой специализации:
Здесь dependent_false_v<T> зависит от T, поэтому утверждение не обязано вычисляться как ложное на этапе определения шаблона. Если выбирается целочисленная ветка, else является отброшенной веткой if constexpr, и её тело для этой специализации не инстанцируется. Для std::string ветка становится активной, после чего static_assert выдаёт диагностическое сообщение.
Важно отличать это от обычного if: его невыбранная ветка всё равно обычно компилируется, поэтому такой приём не решает проблему во время обычного выполнения. Также зависимость должна быть реальной: простое переименование false или помещение его в независимую константу не делает выражение зависимым.
Альтернативой могут быть частичная специализация класса, удалённая перегрузка или ограничение через концепт. Специализация подходит для существенно разных реализаций, концепт обычно даёт более декларативное ограничение интерфейса, а зависимый static_assert удобен, когда структура алгоритма общая и нужна точечная диагностика внутри конкретной ветки.
В библиотечном сериализаторе целочисленные и строковые типы обрабатываются напрямую, а для остальных типов требуется отдельный адаптер. Рассматривались три варианта: разнести реализации по частичным специализациям, добавить концепты на каждую перегрузку или оставить общий алгоритм с проверкой в неподдерживаемой ветке.
Частичные специализации давали точный выбор, но увеличивали число сущностей и затрудняли совместное использование общей логики. Концепты обеспечивали хорошую диагностику на границе API, однако для внутренних ветвей алгоритма потребовали бы дополнительных ограничений и перегрузок.
Выбрали общий шаблон с if constexpr и зависимым static_assert: поддерживаемые типы компилировались без лишнего кода, а ошибка для неподдерживаемого типа возникала непосредственно в выбранной ветке. Для публичной функции дополнительно применили концепт, чтобы отсеивать очевидно недопустимые вызовы ещё до тела функции.
static_assert(false) на static_assert(!sizeof(T))?Да, такое выражение обычно является зависимым от T, но это менее ясный и потенциально проблемный идиом. Для неполного типа или некоторых специальных случаев вычисление sizeof(T) само может породить отдельную ошибку. Именованный шаблонный признак вроде dependent_false_v<T> явно выражает намерение и не зависит от свойств самого типа.
if constexpr полностью игнорироваться?Нет. Она должна быть корректно разобрана как часть шаблона, а независимые ошибки могут быть диагностированы ещё до инстанциации. Неинстанцирование означает, что зависимые сущности внутри ветки не проверяются для конкретной специализации, а не то, что текст ветки вообще не анализируется.
static_assert отличается от ограничения концептом?Концепт ограничивает применимость шаблона до входа в его тело и участвует в выборе перегрузки. Зависимый static_assert срабатывает уже внутри выбранной специализации и может проверять условие, связанное с конкретной внутренней веткой алгоритма. Поэтому концепты лучше подходят для контракта интерфейса, а зависимый static_assert — для невозможных внутренних случаев или более точного сообщения в реализации.