Related: Why do standard containers require allocator_type::value_type to be the element type?
It is said that the following has been deprecated since C++17:
template<>
struct allocator<void>;
I wonder whether it is deprecated because the primary template alone is now able to accommodate allocator<void>
, or the use-case of allocator<void>
is deprecated.
If latter, I wonder why. I think allocator<void>
is useful in specifying an allocator not bound to a specific type (so just some schema/metadata).
According to p0174r0
Similarly, std::allocator<void>
is defined so that various template
rebinding tricks could work in the original C++98 library, but it is
not an actual allocator, as it lacks both allocate
and deallocate
member functions, which cannot be synthesized by default from
allocator_traits
. That need went away with C++11 and the void_pointer
and const_void_pointer
type aliases in allocator_traits. However, we
continue to specify it in order to avoid breaking old code that has
not yet been upgraded to support generic allocators, per C++11.
It's not that std::allocator<void>
is deprecated, just it isn't a explicit specialisation.
What it used to look like was something like:
template<class T>
struct allocator {
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
// These would be an error if T is void, as you can't have a void reference
typedef T& reference;
typedef const T& const_reference;
template<class U>
struct rebind {
typedef allocator<U> other;
}
// Along with other stuff, like size_type, difference_type, allocate, deallocate, etc.
}
template<>
struct allocator<void> {
typedef void value_type;
typedef void* pointer;
typedef const void* const_pointer;
template<class U>
struct rebind {
typdef allocator<U> other;
}
// That's it. Nothing else.
// No error for having a void&, since there is no void&.
}
Now, since std::allocator<T>::reference
and std::allocator<T>::const_reference
have been deprecated, there doesn't need to be an explicit specialisation for void
. You can just use std::allocator<void>
, along with std::allocator_traits<std::allocator<void>>::template rebind<U>
to get std::allocator<U>
, you just can't instantiate std::allocator<void>::allocates
.
For example:
template<class Alloc = std::allocator<void>>
class my_class; // allowed
int main() {
using void_allocator = std::allocator<void>;
using void_allocator_traits = std::allocator_traits<void_allocator>;
using char_allocator = void_allocator_traits::template rebind_alloc<char>;
static_assert(std::is_same<char_allocator, std::allocator<char>>::value, "Always works");
// This is allowed
void_allocator alloc;
// These are not. Taking the address of the function or calling it
// implicitly instantiates it, which means that sizeof(void) has
// to be evaluated, which is undefined.
void* (void_allocator::* allocate_mfun)(std::size_t) = &void_allocator::allocate;
void_allocator_traits::allocate(alloc, 1); // calls:
alloc.allocate(1);
}