std::allocator_traits defaults with allocator that has more than one template parameter
Asked Answered
A

1

5

std::allocator_traits works its magic automatically when I provide an STL-style container with an allocator that has a single template parameter but it doesn't when I provide an STL-style container with an allocator that has two template parameters but is otherwise similar.

What do I need to do to tell std::allocator_traits how to interact with an allocator that has more than one template parameter? Is it possible to get std::allocator_traits to provide reasonable defaults in this case?

As an example, if I take the simple allocator Howard Hinnant provides in Allocator Boilerplate and feed it to a std::vector<> then all is well. If I add a dummy int parameter to the allocator template (and make slight modifications as needed) then I get compiler errors because the compiler couldn't find rebind, among other things.

Here's that description in code:

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

If I have to specialize std::allocator_traits myself in this case, is there a way to still get the defaults?

Arsenate answered 18/1, 2016 at 18:8 Comment(8)
@bolov I want std::allocator_traits to fill in most of the details for me though. Is there any advantage to specializing std::allocator_traits vs completing the allocator concept without it?Arsenate
@bolov that is not recommended, just provide your own rebind in the allocator, see my anwer.Taratarabar
Start un-commenting some of the things in "Allocator Boilerplate" to fix the compile-time errors. Of course you'll have to do more than uncomment, you'll have to make whatever you do uncomment consistent with your allocator. Start with rebind. That may be the only other thing you have to not default, which would be a lot easier than specializing allocator_traits.Salliesallow
@HowardHinnant curiuously, libc++ does compile with the OP's example, whereas libstdc++ doesn't. Do you know/remember what libc++ does for allocators with non-type extra parameters (I glanced at the source, but it seemed like only the regular Alloc<T, Args...> was being matched.Taratarabar
@TemplateRex: My best guess is that libc++'s vector doesn't bother calling rebind. The OP's example will likely fail with another container that must call rebind.Salliesallow
@HowardHinnant just checked, with std::list instead of std::vector, libc++ also doesn't compile. See also this Q&A where @JonathanWakely explains libstdc++ practice of always going through rebind.Taratarabar
@TemplateRex: Yeah, libc++ actively rejects the idea that accepting/compiling vector<int, std::allocator<char>> is doing the client a favor. libc++ goes out of its way to notify the client asap if there is a mismatch like this between the allocator and container: github.com/llvm-mirror/libcxx/blob/master/include/… The motivation for this behavior is because program logic may depend on two independently declared containers having the same type. And if their type differs only by accidentally misdeclared allocators, best to know at compile time, asap.Salliesallow
Fwiw, I learned this lesson years before while developing the CodeWarrior std::lib.Salliesallow
T
8

The Standard only provides a default rebind for allocators with multiple template type parameters:

17.6.3.5 Allocator requirements [allocator.requirements]

3 Note A: The member class template rebind in the table above is effectively a typedef template. [ Note: In general, if the name Allocator is bound to SomeAllocator<T>, then Allocator::rebind<U>::other is the same type as SomeAllocator<U>, where SomeAllocator<T>::value_type is T and SomeAllocator<U>:: value_type is U. — end note ] If Allocator is a class template instantiation of the form SomeAllocator<T, Args>, where Args is zero or more type arguments, and Allocator does not supply a rebind member template, the standard allocator_traits template uses SomeAllocator<U, Args> > in place of Allocator:: rebind<U>::other by default. For allocator types that are not template instantiations of the above form, no default is provided.

Since you have a non-type (int) parameter, there is no default provided. The fix is simple: just add your own rebind to your allocator.

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

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

Live Example

As to the rationale of allowing for allocators of the form Allocator<T, Args...> but not for allocators of the form Alloc<T, Ns...>, one can only guess, but then it would also lead to the plethora of Alloc<T, Args.., Ns...> etc. etc.. This is why template-metaprogramming libraries (such as Boost.MPL) always wrap their non-type parameters N of type T inside things like integral_constant<T, N>. This would also be a route for you, by defining

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>
};

Live Example

Taratarabar answered 18/1, 2016 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.