Назначение аргументов шаблона ссылки

Вы можете использовать ссылку на глобальный объект в качестве параметра шаблона. Например, таким образом:

class A {};

template<A& x>
void fun()
{
}

A alpha;

int main()
{
    fun<alpha>();
}

В какой ситуации может быть полезен аргумент шаблона ссылки?

Ответ 1

Одним из сценариев может быть сильная typedef с токеном идентификации, который должен быть не целочисленного типа, а строкой для простоты использования при сериализации. Затем можно использовать оптимизацию пустого базового класса, чтобы исключить любые дополнительные требования к пространству, которые имеет производный тип.

Пример:

// File: id.h
#pragma once
#include <iosfwd>
#include <string_view>

template<const std::string_view& value>
class Id {
    // Some functionality, using the non-type template parameter...
    // (with an int parameter, we would have some ugly branching here)
    friend std::ostream& operator <<(std::ostream& os, const Id& d)
    {
        return os << value;
    }

    // Prevent UB through non-virtual dtor deletion:
    protected:
      ~Id() = default;
};

inline const std::string_view str1{"Some string"};
inline const std::string_view str2{"Another strinng"};

И в каком-то переводном блоке:

#include <iostream>
#include "id.h"

// This type has a string-ish identity encoded in its static type info,
// but its size isn't augmented by the base class:
struct SomeType : public Id<str2> {};

SomeType x;

std::cout << x << "\n";

Ответ 2

Справочные параметры шаблона нетипичного типа позволяют вам писать код, который автоматически будет специализирован для работы с конкретным объектом статической длительности хранения. Это чрезвычайно полезно, например, в средах, где ресурсы должны быть статически распределены. Допустим, у нас есть некоторый класс Processor который должен выполнять какую-то обработку, включающую динамическое создание группы объектов. Кроме того, скажем, что хранилище для этих объектов должно исходить из статически выделенного пула памяти. У нас может быть очень простой распределитель, который просто содержит некоторое хранилище и "указатель" на начало свободного места.

template <std::size_t SIZE>
class BumpPoolAllocator
{
    char pool[SIZE];

    std::size_t next = 0;

    void* alloc(std::size_t alignment)
    {
        void* ptr = pool + next;
        next = ((next + alignment - 1) / alignment * alignment);
        return ptr;
    }

public:
    template <typename T, typename... Args>
    T& alloc(Args&&... args)
    {
        return *new (alloc(alignof(T))) T(std::forward<Args>(args)...);
    }
};

а затем статически выделить пул памяти некоторого размера, поместив экземпляр где-нибудь в статическое хранилище:

BumpPoolAllocator<1024*1024> pool_1;

Теперь у нас может быть Processor который может работать с любым видом пула памяти.

template <typename T, typename Pool>
class Processor
{
    Pool& pool;

    // …

public:
    Processor(Pool& pool) : pool(pool) {}

    void process()
    {
        // …

        auto bla = &pool.template alloc<T>();

        // …
    }
};

а затем также выделить один из тех, кто статически

Processor<int, decltype(pool_1)> processor_1(pool_1);

Но обратите внимание, что каждый такой экземпляр Processor теперь по существу содержит поле, содержащее адрес объекта пула, который на самом деле является постоянной, известной во время компиляции. И каждый раз, когда наш Processor что-то делает со своим pool, адрес pool будет выбираться из памяти просто для того, чтобы всегда обращаться к одному и тому же объекту пула, расположенному по адресу, который фактически был бы известен во время компиляции. Если мы уже распределяем все статически, мы могли бы также воспользоваться тем фактом, что местоположение всего известно во время компиляции, чтобы избавиться от ненужных косвенных указаний. Используя параметр шаблона ссылки, мы можем сделать это:

template <typename T, auto& pool>
class Processor
{
    // …

public:
    void process()
    {
        // …

        auto bla = &pool.template alloc<T>();

        // …
    }
};

Processor<int, pool_1> processor_1;

Вместо того чтобы каждый объект Processor удерживал адрес пула, который он должен использовать, мы специализируем весь Processor на непосредственном использовании определенного объекта пула. Это позволяет нам избавиться от любых ненужных косвенных указаний, адрес используемого пула будет практически везде указываться везде. В то же время мы сохраняем гибкость, позволяющую свободно создавать пулы и процессоры любым удобным для нас способом:

BumpPoolAllocator<1024*1024> pool_1;  // some pool
BumpPoolAllocator<4*1024> pool_2;     // another, smaller pool


Processor<int, pool_1> processor_1;   // some processor

struct Data {};
Processor<Data, pool_1> processor_2;  // another processor using the same pool

Processor<char, pool_2> processor_3;  // another processor using the smaller pool

Одной из сред, где я постоянно использую параметры эталонного шаблона, является GPU. Существует ряд обстоятельств, которые делают шаблоны в целом, и, в частности, ссылки на параметры шаблонов, чрезвычайно мощным (я бы даже сказал: существенным) инструментом для программирования на GPU. Прежде всего, единственная причина для написания кода для GPU - это производительность. Динамическое выделение памяти из некоторой глобальной кучи общего назначения обычно не подходит для графического процессора (большие накладные расходы). Всякий раз, когда требуется динамическое распределение ресурсов, это обычно делается с использованием какого-то специально созданного ограниченного пула. Работа со смещениями относительно статического базового адреса может быть полезной (если достаточно 32-битных индексов) по сравнению с выполнением того же действия с арифметикой со значениями во время выполнения, так как графические процессоры обычно имеют 32-битные регистры, а число используемых регистров может быть ограничивающий фактор уровня параллелизма, которого можно достичь. Таким образом, статическое распределение ресурсов и избавление от косвенных указаний обычно привлекательны для кода GPU. В то же время стоимость косвенных вызовов функций на GPU, как правило, непомерно высока (из-за количества состояний, которые должны быть сохранены и восстановлены), что означает, что об использовании полиморфизма времени выполнения для гибкости обычно не может быть и речи. Шаблоны со ссылочными параметрами шаблона дают нам именно то, что нам нужно: способность выражать сложные операции над сложными структурами данных таким образом, чтобы он был полностью гибким вплоть до момента компиляции, но компилировался в самый жесткий и эффективный двоичный файл.

По тем же причинам, я считаю, что параметры эталонного шаблона очень полезны, например, также во встроенных системах…