std::allocator_traits по умолчанию с распределителем, который имеет более одного параметра шаблона

std::allocator_traits творит чудеса автоматически, когда я предоставляю контейнер в стиле STL с распределителем, имеющим один параметр шаблона, но не работает, когда я предоставляю контейнер в стиле STL с распределителем, имеющим два параметра шаблона, но в остальном похожим.

Что мне нужно сделать, чтобы сообщить std::allocator_traits, как взаимодействовать с распределителем, который имеет более одного параметра шаблона? Можно ли в этом случае заставить std::allocator_traits предоставить разумные значения по умолчанию?

В качестве примера, если я возьму простой распределитель, который Говард Хиннант предоставил в Allocator Boilerplate, и передам его std::vector<> значит все хорошо. Если я добавлю фиктивный параметр int в шаблон allocator (и при необходимости внесу небольшие изменения), то я получу ошибки компилятора, потому что компилятор, среди прочего, не смог найти rebind.

Вот это описание в коде:

http://coliru.stacked-crooked.com/a/173c57264137a351

Если в этом случае мне придется специализироваться std::allocator_traits, есть ли способ получить значения по умолчанию?


person Praxeolitic    schedule 18.01.2016    source источник
comment
@bolov Я хочу, чтобы std::allocator_traits заполнил за меня большую часть деталей. Есть ли какое-либо преимущество в специализации std::allocator_traits по сравнению с завершением концепции распределителя без него?   -  person Praxeolitic    schedule 18.01.2016
comment
@bolov это не рекомендуется, просто укажите свой собственный rebind в распределителе, смотрите мой ответ.   -  person TemplateRex    schedule 18.01.2016
comment
Начните раскомментировать некоторые вещи в Allocator Boilerplate, чтобы исправить ошибки времени компиляции. Конечно, вам придется сделать больше, чем просто раскомментировать, вам нужно будет раскомментировать все, что вы делаете, в соответствии с вашим распределителем. Начните с rebind. Это может быть единственное, что вам нужно не делать по умолчанию, что было бы намного проще, чем специализация allocator_traits.   -  person Howard Hinnant    schedule 19.01.2016
comment
@HowardHinnant, что любопытно, libc++ компилируется с примером OP, а libstdc++ - нет. Вы знаете/помните, что libc++ делает для распределителей с дополнительными параметрами, не относящимися к типу (я взглянул на источник, но мне показалось, что сопоставляется только обычный Alloc<T, Args...>.   -  person TemplateRex    schedule 19.01.2016
comment
@TemplateRex: я думаю, что vector libc++ не вызывает rebind. Пример OP, скорее всего, завершится ошибкой с другим контейнером, который должен вызывать rebind.   -  person Howard Hinnant    schedule 19.01.2016
comment
@HowardHinnant только что проверил, с std::list вместо std::vector libc++ также не компилируется. См. также эти вопросы и ответы, где @JonathanWakely объясняет практику libstdc++ всегда проходить через rebind.   -  person TemplateRex    schedule 19.01.2016
comment
@TemplateRex: Да, libc++ активно отвергает идею о том, что принятие/компиляция vector<int, std::allocator<char>> оказывает клиенту услугу. libc++ изо всех сил старается уведомить клиента как можно скорее, если между распределителем и контейнером есть несоответствие, подобное этому: github.com/llvm-mirror/libcxx/blob/master/include/ Мотивация такого поведения заключается в том, что логика программы может зависеть от двух независимо объявленных контейнеров, имеющих того же типа. И если их тип отличается только случайно неправильно объявленными распределителями, лучше всего знать это во время компиляции, как можно скорее.   -  person Howard Hinnant    schedule 19.01.2016
comment
Между прочим, я усвоил этот урок много лет назад, когда разрабатывал CodeWarrior std::lib.   -  person Howard Hinnant    schedule 19.01.2016


Ответы (1)


Стандарт предоставляет значение по умолчанию rebind только для распределителей с несколькими параметрами шаблона type:

17.6.3.5 Требования к распределителю [allocator.requirements]

3 Примечание A: Шаблон класса-члена rebind в приведенной выше таблице фактически является шаблоном 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 не предоставляет шаблон члена rebind, стандартный шаблон allocator_traits использует SomeAllocator<U, Args> > в место Allocator:: rebind<U>::other по умолчанию. Для типов распределителей, которые не являются экземплярами шаблона вышеуказанной формы, значения по умолчанию не предоставляются.

Поскольку у вас есть параметр, не являющийся типом (int), значение по умолчанию не предусмотрено. Исправление простое: просто добавьте свою собственную перепривязку в свой аллокатор.

template<class T, int I>
class allocator_w_int
{
    // as before

    template<class U>
    struct rebind { using other = allocator_w_int<U, I>; };    
};

Живой пример

Что касается обоснования разрешения аллокаторов формы Allocator<T, Args...>, но не аллокаторов формы Alloc<T, Ns...>, можно только догадываться, но тогда это также привело бы к изобилию Alloc<T, Args.., Ns...> и т. д. и т. д. Вот почему библиотеки метапрограммирования шаблонов ( такие как Boost.MPL) всегда заключают свои нетиповые параметры N типа T в такие вещи, как integral_constant<T, N>. Это также будет маршрутом для вас, определив

template<class T, class Arg>
class allocator_w_int; // leave undefined

template<int N>
using int_ = std::integral_constant<int, N>;

template<class T, int I>
class allocator_w_int<T, int_<I>>
{
    // replace all occurances of I, J --> int_<I>, int_<J>
};

Живой пример

person TemplateRex    schedule 18.01.2016