Шаблоны должны использоваться в заголовках, потому что компилятор должен создавать экземпляры разных версий кода в зависимости от параметров, заданных / выведенных для параметров шаблона. Помните, что шаблон не представляет собой код напрямую, а шаблон для нескольких версий этого кода. Когда вы компилируете функцию non-template в файле .cpp
, вы компилируете конкретную функцию / класс. Это не относится к шаблонам, которые могут быть созданы с использованием разных типов, а именно, если при замене параметров шаблона конкретными типами необходимо исправить конкретный код.
Была функция с ключевым словом export
, которая была предназначенный для отдельной компиляции. Функция export
устарела в C++11
и, AFAIK, только один компилятор реализовал ее. Вы не должны использовать export
. Отдельная компиляция невозможна в C++
или C++11
, но, возможно, в C++17
, если понятия в нее входят, мы могли бы иметь некоторый способ отдельной компиляции.
Для отдельной компиляции, которая должна быть достигнута, разделить проверка шаблона тела должна быть возможна. Кажется, что решение возможно с концепциями. Взгляните на этот документ , недавно представленный на совещании по стандартам. Я думаю, что это не единственное требование, поскольку вам все равно необходимо создать код кода шаблона в коде пользователя.
Отдельная проблема компиляции для шаблонов, я думаю, это также проблема, возникающая при миграции на модули, которые в настоящее время работают.
Во-первых, вы должны понимать «обычные арифметические преобразования» (эта ссылка для C, но правила в C ++ те же). В C ++, если вы выполняете арифметику со смешанными типами (вам следует избегать этого, когда это возможно, кстати), есть набор правил, которые решают, в каком типе выполняется вычисление.
В вашем случае вы вычитаете подписанное int из unsigned int. Правила продвижения гласят, что фактический расчет выполняется с использованием unsigned int
.
Таким образом, вы вычислили 10 - 16
в беззнаковой арифметике. Арифметика без знака является арифметикой по модулю, что означает, что она оборачивается. Итак, при условии вашего типичного 32-битного целого, результат этого вычисления 2 ^ 32 - 6.
Это одинаково для обеих линий. Обратите внимание, что вычитание полностью не зависит от назначения; тип слева не имеет абсолютно никакого влияния на то, как происходит вычисление. Распространенной ошибкой новичка считается, что тип слева как-то влияет на вычисление; но float f = 5 / 6
равно нулю, потому что деление все еще использует целочисленную арифметику.
Разница в том, что происходит во время назначения. Результат вычитания неявно преобразуется в float
в одном случае и int
в другом.
Преобразование во float пытается найти самое близкое значение к фактическому, которое может представлять тип. Это будет очень большое значение; не совсем то, что было получено в оригинальном вычитании.
Преобразование в int говорит о том, что если значение попадает в диапазон int, значение не изменится. Но 2 ^ 32 - 6 намного больше, чем 2 ^ 31 - 1, который может содержать 32-битное целое, поэтому вы получаете другую часть правила преобразования, которая говорит, что полученное значение определяется реализацией. Это термин в стандарте, который означает, что «разные компиляторы могут делать разные вещи, но они должны документировать, что они делают».
Для всех практических целей все компиляторы, с которыми вы, вероятно, столкнетесь, скажут, что битовая комбинация остается неизменной и просто интерпретируется как подписанная. Из-за того, как работает арифметика дополнения 2 (так, что почти все компьютеры представляют отрицательные числа), в результате вы получите -6, который можно ожидать из расчета.
Но все это очень длинный способ повторения первого пункта, который гласит: «не делайте арифметику смешанного типа». Сначала приведите типы явно к типам, которые, как вы знаете, будут делать правильные вещи.