C++ standard containers and allocators provide typedefs for the pointer type used by the container, i.e.:
typename std::vector<T>::pointer
typename std::vector<T>::const_pointer
The actual pointer type used to create the typedef is determined via std::allocator_traits
typedef typename std::allocator_traits<Allocator>::pointer pointer;
Since each container also has a value_type
typedef, presumably the purpose of the pointer
typedef is for some weird situation where the pointer type used is something other than value_type*
. I've never personally seen a use case for something like that, but I suppose the standards committee wanted to provide the possibility of using custom pointer types with containers.
The problem is that this seems to be inconsistent with the definitions provided for the functions in std::allocator_traits
. Specifically, in std::allocator_traits
we have the construct
function, which is defined as:
template <class T, class... Args>
static void construct(Alloc& a, T* p, Args&&... args);
...which just calls a.construct(p, std::forward<Args>(args)...)
But note that this function makes no provisions for a custom pointer type. The parameter p
is a plain-old native pointer.
So, why isn't the definition of this function something like:
template <class... Args>
static void construct(Alloc& a, typename Alloc::pointer p, Args&&... args);
It seems without this, containers which used std::allocator_traits<Alloc>::construct
would fail if used with an Allocator that defines some custom pointer type.
So, what's going on here? Or am I misunderstanding the purpose of having pointer
typedefs in the first place?
This dichotomy is purposeful, and does not present a problem. The construct
member function is typically implemented like this:
template <class U, class ...Args>
void
construct(U* p, Args&& ...args)
{
::new(static_cast<void*>(p)) U(std::forward<Args>(args)...);
}
I.e. it forwards to placement new
, which in turn has this signature:
void* operator new (std::size_t size, void* ptr) noexcept;
So ultimately you need a "real" pointer to call placement new. And to convey the type of the object that needs to be constructed, it just makes sense to pass that information along in the pointer type (e.g. U*
).
For symmetry, destroy
is also formulated in terms of an actual pointer and is typically implemented like so:
template <class U>
void
destroy(U* p)
{
p->~U();
}
The main use case for the "fancy pointer" is to place objects into shared memory. A class called offset_ptr
is typically used for this purpose, and an allocator can be created to allocate and deallocate memory referred to by offset_ptr
. And thus the allocator_traits
and allocator
allocate
and deallocate
functions traffic in terms of pointer
instead of value_type*
.
So the question arises: If you've got a pointer
, and need a T*
, what do you do?
There are two techniques I'm aware of for creating a T*
from a pointer p
1.
std::addressof(*p);
When you dereference a pointer p
, it must result in an lvalue according to the standard. However it would be nice to be able to relax this requirement (e.g. consider a pointer
returning a proxy reference such as vector<bool>::reference
). std::addressof
is specified to to return a T*
to any lvalue:
template <class T> T* addressof(T& r) noexcept;
2.
to_raw_pointer(p); // where:
template <class T>
inline
T*
to_raw_pointer(T* p) noexcept
{
return p;
}
template <class Pointer>
inline
typename std::pointer_traits<Pointer>::element_type*
to_raw_pointer(Pointer p) noexcept
{
return ::to_raw_pointer(p.operator->());
}
This calls a pointer
's operator->()
, which will either directly return a T*
or forward to something that will either directly or indirectly return a T*
. All pointer
types should support operator->()
, even if it is referencing a bool
. A downside of this technique is that currently operator->()
is required to not be called unless the pointer
is dereferenecable. That restriction should be lifted in the standard.
In C++14 the return type of the second overload (well actually both overloads), can be conveniently replaced with auto
.
If you have a T*
and wish to construct a pointer
, you are out of luck. There is no portable way to convert in this direction.
Also note this tangentially related LWG issue about the vector::data()
member function's return type. It has bounced between value_type*
, pointer
and back, and is currently (and purposefully) value_type*
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With