Где можно писать шаблоны (templates) в C++?

Ответ

Шаблоны в C++ можно писать в различных местах, но основное правило связано с моделью компиляции: компилятору нужен полный код шаблона в момент его инстанцирования (создания конкретной версии для типа).

Основные места размещения:

  1. Заголовочные файлы (.hpp/.h): Наиболее распространенный и удобный способ. Весь код шаблона (объявление и определение) находится в заголовке.

    // vector_utils.hpp
    #pragma once
    
    template <typename T>
    class SimpleVector {
    private:
        T* data_;
        size_t size_;
    public:
        explicit SimpleVector(size_t size) : size_(size), data_(new T[size]) {}
        ~SimpleVector() { delete[] data_; }
        T& operator[](size_t index) { return data_[index]; }
    };
    
    template <typename T>
    T sum(const SimpleVector<T>& vec) { /* реализация здесь */ }
  2. Явное инстанцирование в .cpp файле: Можно вынести определение шаблона в .cpp файл, но тогда необходимо явно инстанцировать его для всех используемых типов. Это сокращает время компиляции.

    // my_template.cpp
    template <typename T>
    T max(T a, T b) { return (a > b) ? a : b; }
    
    // Явное инстанцирование для int и double
    template int max<int>(int, int);
    template double max<double>(double, double);

    В заголовке останется только объявление: template <typename T> T max(T a, T b);.

  3. Внутри классов/структур: Шаблонные методы можно определять прямо внутри класса.

    template <typename Key, typename Value>
    class Cache {
    public:
        // Шаблонный метод внутри шаблонного класса
        template <typename InputIt>
        void insertRange(InputIt first, InputIt last) {
            for (; first != last; ++first) {
                storage_.insert(*first);
            }
        }
    private:
        std::map<Key, Value> storage_;
    };

Рекомендация: Для большинства проектов полное определение шаблонов в заголовочных файлах — это стандартный и наименее проблемный подход.

Ответ 18+ 🔞

Ёпта, ну смотри, шаблоны в C++ — это как хитрая жопа, которую можно запихнуть в разные места, но главное правило — компилятору нужен весь код шаблона, когда он начинает из него конкретную хуйню для твоего типа делать. Это называется инстанцированием, если ты вдруг забыл.

Куда их, блядь, можно пихать:

  1. В заголовочные файлы (.hpp/.h): Самый, блядь, популярный и простой способ. Всю подноготную шаблона — и объявление, и определение — суёшь прямо в хедер. Компилятор потом везде, где этот хедер инклюдят, видит всё целиком и спокоен, как удав.

    // vector_utils.hpp
    #pragma once
    
    template <typename T>
    class SimpleVector {
    private:
        T* data_;
        size_t size_;
    public:
        explicit SimpleVector(size_t size) : size_(size), data_(new T[size]) {}
        ~SimpleVector() { delete[] data_; }
        T& operator[](size_t index) { return data_[index]; }
    };
    
    template <typename T>
    T sum(const SimpleVector<T>& vec) { /* реализация здесь */ }
  2. Явное инстанцирование в .cpp файле: Вот это уже для тех, кто хочет скомпилироваться побыстрее. Ты выносишь реализацию шаблона в .cpp, но тогда должен прямо там, в этом же файле, перечислить все типы, для которых ты его будешь использовать. А в заголовке оставляешь только обещание, что такая функция будет. Если забудешь тип указать — получишь ошибку линковки, и будешь потом ебать мои старые костыли, пока ищешь, где же твой max для float потерялся.

    // my_template.cpp
    template <typename T>
    T max(T a, T b) { return (a > b) ? a : b; }
    
    // Явное инстанцирование для int и double
    template int max<int>(int, int);
    template double max<double>(double, double);

    В заголовке останется только строчка: template <typename T> T max(T a, T b);. Доверия к такому подходу, блядь, ноль, пока сам всё не проверишь.

  3. Прямо внутри классов: Ну это вообще ебушки-воробушки. Шаблонный метод внутри шаблонного же класса. Получается матрёшка, пиздец. Но иногда удобно.

    template <typename Key, typename Value>
    class Cache {
    public:
        // Шаблонный метод внутри шаблонного класса
        template <typename InputIt>
        void insertRange(InputIt first, InputIt last) {
            for (; first != last; ++first) {
                storage_.insert(*first);
            }
        }
    private:
        std::map<Key, Value> storage_;
    };

Вывод, ёпта: Если не хочешь себе геморроя и удивления на уровне «ни хуя себе, почему не линкуется», просто пихай все шаблоны целиком в заголовки. Это стандартно, предсказуемо и мозг не ебёт.