На каком основании два экземпляра шаблона с разными нетиповыми значениями считаются разными типами?
Нетиповой параметр шаблона входит в идентичность специализации. Поэтому специализации с одинаковыми типами, но разными допустимыми значениями такого параметра являются разными типами: они могут иметь разный размер, участвовать в разных перегрузках и вызывать разные ветви метапрограммирования.
Если значения нетиповых аргументов эквивалентны по правилам языка, используется одна и та же специализация. Для параметров вида auto учитывается не только значение, но и выведенный тип параметра.
Шаблоны в C++ предназначены не только для обобщения алгоритмов по типам. Нетиповые параметры позволяют описывать на этапе компиляции такие свойства, как размер буфера, индекс, маска флагов или размерность структуры.
Это решает задачу разделения конфигураций, известных во время компиляции, без хранения этих параметров в объекте и без обязательных проверок во время выполнения. Цена подхода — каждая комбинация параметров образует отдельную специализацию и может увеличивать объём генерируемого кода.
Рассмотрим шаблон буфера с параметрами типа элемента и его размера. Буфер на четыре элемента и буфер на восемь элементов должны иметь разные типы: присваивание одного другому небезопасно, а операции над ними могут требовать разной реализации.
Если размер хранить только как обычное поле объекта, типы таких буферов совпадут. Тогда проверка совместимости размеров переместится во время выполнения или будет полностью возложена на программиста.
Для каждого набора шаблонных аргументов компилятор формирует специализацию. В случае нетипового параметра его значение является частью набора аргументов, поэтому Буфер<int, 4> и Буфер<int, 8> — разные типы, даже при одинаковом int.
Для целочисленных, перечислимых и некоторых других категорий аргументов сравнивается их значение с учётом правил эквивалентности шаблонных аргументов. Например, два константных выражения, вычисляющие одно и то же значение для параметра типа int, обозначают одну специализацию.
У параметра auto важен и выведенный тип. Значения 1 и 1u обычно создают разные специализации шаблона с параметром auto, поскольку имеют разные типы — int и unsigned int, несмотря на одинаковое числовое представление.
Нетиповой аргумент обязан удовлетворять ограничениям соответствующего параметра и быть допустимым константным выражением либо объектом разрешённой структурной категории. В современных стандартах C++ допустимые категории шире, чем только целые числа, но произвольный изменяемый объект использовать как нетиповой аргумент нельзя.
Разные значения могут влиять не только на типобезопасность. Они позволяют выбирать частичные специализации, вычислять размеры объектов, формировать ветви if constexpr и передавать значение в код как известную во время компиляции константу. Обратная сторона — рост времени компиляции и размера бинарного файла при большом числе комбинаций.
Важно отличать идентичность специализации от равенства объектов. Два объекта одного типа могут содержать разные значения, но специализации с разными нетиповыми аргументами различаются уже на уровне типов. Для структурных нетиповых параметров правила эквивалентности определяются правилами шаблонных аргументов, а не произвольным пользовательским operator==.
В библиотеке сетевых пакетов требуется поддержать пакеты фиксированного размера. Рассматривались два варианта: хранить размер как поле обычного класса или включить его в нетиповой параметр шаблона.
Поле обычного класса проще использовать с динамическими размерами и не создаёт отдельные типы для каждого размера. Однако ошибки соединения пакетов несовместимых размеров обнаруживаются поздно, а оптимизатору сложнее использовать размер как гарантированную константу.
Нетиповой параметр делает размер частью типа. Это позволяет отклонить несовместимое присваивание на этапе компиляции и выбрать специализированную реализацию копирования или сериализации. Недостатки — отдельная специализация для каждого размера и необходимость применять отдельные функции преобразования, если размер нужно менять динамически.
Для фиксированных протокольных форматов выбран нетиповой параметр: размер известен заранее, а ранняя диагностика важнее универсальности. Для произвольных пользовательских данных оставлен отдельный динамический тип, чтобы не порождать большое количество специализаций.
1. Совпадут ли специализации, если нетиповые аргументы записаны по-разному, но дают одно значение?
Для параметра типа int эквивалентные константные выражения, вычисляющие одно значение, обозначают одну специализацию. Форма записи выражения сама по себе не обязана сохранять различие. Но это правило нельзя безоговорочно переносить на указатели, ссылки и структурные параметры: там действуют дополнительные требования и правила эквивалентности.
2. Почему значения 1 и 1u могут создать разные специализации параметра auto?
auto в нетиповом параметре сначала выводит тип аргумента. Для 1 выводится int, для 1u — unsigned int. В результате различается не только значение, но и тип самого параметра, поэтому специализации не считаются одной и той же.
3. Почему нетиповым параметром нельзя сделать произвольный объект с изменяемым состоянием?
Компилятор должен однозначно определить шаблонный аргумент во время компиляции и использовать его для идентификации специализации. Произвольный изменяемый объект не обладает необходимыми свойствами константности, адресной стабильности или структурной сравнимости. Поэтому язык допускает только определённые категории значений и объектов, удовлетворяющих требованиям к constant template parameter; соответствие пользовательскому operator== само по себе таких прав не даёт.