Почему необходим allocator:: rebind, когда у нас есть параметры шаблона шаблона?

Каждый класс распределителя должен иметь интерфейс, похожий на следующий:

template<class T>
class allocator
{
    ...
    template<class Other>
    struct rebind { typedef allocator<Other> other; };
};

И классы, которые используют распределители, делают что-то лишнее:

template<class T, class Alloc = std::allocator<T> >
class vector { ... };

Но зачем это нужно?

Другими словами, разве они не могли просто сказать:

template<class T>
class allocator { ... };

template<class T, template<class> class Alloc = std::allocator>
class vector { ... };

который является более элегантным, менее избыточным и (в некоторых подобных ситуациях) потенциально безопаснее?
Почему они прошли маршрут rebind, что также приводит к большей избыточности (т.е. Вы должны сказать T дважды)?

(Аналогичный вопрос относится к char_traits, а остальное... хотя они не все имеют rebind, они все равно могут воспользоваться параметрами шаблона шаблона.)


Edit:

Но это не сработает, если вам понадобится более одного параметра шаблона!

Собственно, он работает очень хорошо!

template<unsigned int PoolSize>
struct pool
{
    template<class T>
    struct allocator
    {
        T pool[PoolSize];

        ...
    };
};

Теперь, если vector определяется только так:

template<class T, template<class> class Alloc>
class vector { ... };

Тогда вы могли бы просто сказать:

typedef vector<int, pool<1>::allocator> int_vector;

И это будет работать отлично, без необходимости (в избытке) сказать int дважды.

И операция rebind внутри vector просто станет Alloc<Other> вместо Alloc::template rebind<Other>::other.

Ответ 1

Цитата из Основы алгоритмов в С++ 11, том 1, глава 4, с. 35:

template <typename T> 
struct allocator 
{  
   template <typename U>  
   using  rebind = allocator<U>; 
}; 

Использование образца:

allocator<int>::rebind<char> x;

В языке программирования С++, четвертое издание, раздел 34.4.1, стр. 998, комментируя "классический" элемент обратной связи в классе распределителя по умолчанию:

template<typename U>
     struct rebind { using other = allocator<U>;};

Бьярне Страуструп пишет следующее:

Любопытный шаблон перезаписи - это архаичный псевдоним. Это должно было быть:

template<typename U>
using other = allocator<U>;

Однако распределитель был определен до того, как такие псевдонимы были поддержаны С++.

Ответ 2

Но зачем это нужно?

Что делать, если ваш класс распределителя содержит более одного аргумента шаблона?

В значительной степени это связано с тем, почему обычно не рекомендуется использовать аргументы шаблона шаблона в пользу использования обычных аргументов шаблона, даже если это означает немного избыточности на сайте-экземпляре. Во многих случаях (но, вероятно, не для распределителей) этот аргумент может не всегда быть шаблоном класса (например, нормальным классом с функциями члена шаблона).

Вам может показаться удобным (в рамках реализации класса контейнера) использовать параметр шаблона шаблона только потому, что он упрощает некоторые внутренние синтаксисы. Однако, если у пользователя есть шаблон класса с несколькими аргументами в качестве распределителя, который он хочет использовать, но вы требуете от пользователя предоставить распределитель, который является шаблоном класса с одним аргументом, вы фактически заставите его создать оболочку для почти любой новый контекст, в котором он должен использовать этот распределитель. Это не только невозможно, но и неудобно. И на данный момент это решение далеко не является "изящным и менее избыточным" решением, которое вы изначально предполагали. Скажем, у вас был распределитель с двумя аргументами, какой из следующих является самым простым для пользователя?

std::vector<T, my_allocator<T,Arg2> > v1;

std::vector<T, my_allocator_wrapper<Arg2>::template type > v2;

Вы в основном заставляете пользователя создавать много бесполезных вещей (обертки, псевдонимы шаблонов и т.д.) только для удовлетворения ваших потребностей в реализации. Требование, чтобы автор настраиваемого класса распределителя для поставки вложенного шаблона повторной обработки (который является просто тривиальным псевдонимом шаблона) намного проще, чем все требуемые искажения с альтернативным подходом.

Ответ 3

В вашем подходе вы вынуждаете распределитель быть шаблоном с одним параметром, что может быть не всегда так. Во многих случаях распределители могут быть не-шаблонами, а вложенный rebind может возвращать один и тот же тип распределителя. В других случаях распределитель может иметь дополнительные аргументы шаблона. Этот второй случай - это случай std::allocator<>, который, поскольку всем шаблонам в стандартной библиотеке разрешено иметь дополнительные аргументы шаблона, пока реализация предоставляет значения по умолчанию. Также обратите внимание, что существование rebind является необязательным в некоторых случаях, где allocator_traits можно использовать для получения типа отскока.

В стандарте фактически упоминается, что вложенный rebind на самом деле просто шаблонный typedef:

§17.6.3.5/3  Примечание A: Повторение шаблона шаблона класса участников в приведенной выше таблице эффективно шаблон typedef. [Примечание: в целом, если имя Allocator привязан к SomeAllocator<T>, затем Allocator::rebind<U>::other - это тот же тип, что и SomeAllocator<U>, где someAllocator<T>::value_type - T, а SomeAllocator<U>::value_type - U. - примечание к концу] Если Allocator является шаблоном класса создание формы SomeAllocator<T, Args>, где Args равно нулю или более аргументов типа, а Allocator не предоставляет элемент повторной привязки шаблон, стандартный шаблон allocator_traits использует SomeAllocator<U, Args> вместо Allocator:: rebind<U>::other по умолчанию. Для типы распределителей, которые не являются экземплярами шаблонов выше форма, по умолчанию не предоставляется.

Ответ 4

Предположим, вы хотите написать функцию, которая принимает все виды векторов.

Тогда гораздо удобнее писать

template <class T, class A>
void f (std::vector <T, A> vec) {
   // ...
}

чем писать

template <class T, template <class> class A>
void f (std::vector <T, A> vec) {
   // ...
}

В большинстве случаев такая функция все равно не заботится о распределителе.

Далее следует отметить, что распределители не должны быть шаблоном. Вы можете написать отдельные классы для определенных типов, которые необходимо выделить.

Еще более удобный способ проектирования распределителей, вероятно, был

struct MyAllocator { 
   template <class T>
   class Core {
      // allocator code here
   };
};

Тогда было бы возможно написать

std::vector <int, MyAllocator> vec;

а не несколько вводящее в заблуждение выражение

std::vector <int, MyAllocator<int> > vec;

Я не уверен, разрешено ли вышеуказанное MyAllocator использовать в качестве распределителя после добавления rebind, то есть ли допустимый класс распределителя:

struct MyAllocator { 
   template <class T>
   class Core {
      // allocator code here
   };

   template <class T>
   struct rebind { using other=Core<T>; };
};