Назначение аргументов шаблона ссылки - PullRequest
28 голосов
/ 25 апреля 2019

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

class A {};

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

A alpha;

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

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

Ответы [ 2 ]

18 голосов
/ 25 апреля 2019

Один сценарий может быть сильной 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";
4 голосов
/ 08 мая 2019

Что позволяют сделать параметры шаблона нетипового шаблона, это написать код, который будет автоматически специализирован для работы с конкретным объектом статической длительности хранения.Это чрезвычайно полезно, например, в средах, где ресурсы должны быть статически распределены.Допустим, у нас есть некоторый класс 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 - это производительность.Динамическое выделение памяти из некоторой глобальной кучи общего назначения обычно не подходит для графического процессора (большие накладные расходы).Всякий раз, когда требуется динамическое распределение ресурсов, это обычно делается с использованием какого-то специально созданного ограниченного пула.Работа со смещениями относительно статического базового адреса может быть полезной (если достаточно 32-битных индексов) по сравнению с выполнением того же действия с арифметикой со значениями во время выполнения, так как графические процессоры обычно имеют 32-битные регистры, а число используемых регистров можетограничивающий фактор уровня параллелизма, которого можно достичь.Таким образом, статическое распределение ресурсов и избавление от косвенных указаний обычно привлекательны для кода GPU.В то же время стоимость косвенных вызовов функций на GPU, как правило, непомерно высока (из-за количества состояний, которые должны быть сохранены и восстановлены), что означает, что об использовании полиморфизма времени выполнения для гибкости обычно не может быть и речи.Шаблоны со ссылочными параметрами шаблона дают нам именно то, что нам нужно: способность выражать сложные операции над сложными структурами данных таким образом, чтобы он был полностью гибким вплоть до момента, когда вы нажимаете кнопку compile, но компилирует до самого жесткого и эффективного двоичного файла.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...