Reputation: 1134
I'm trying to implement my own allocator, which should work with STL containers and use a custom fancy pointer implementation.
I'm pretty sure, that my classes fulfill all requirements (according to cppreference) but my implementation doesn't compile for std::list because there is no conversion from my fancy pointer to a normal pointer.
A minimal example which shows the problem, (but is obviously not my real implementation):
fancy_ptr.h:
#include <cstddef>
template<typename T>
class FancyPtr {
T *ptr;
FancyPtr(T *ptr, bool) : ptr(ptr) {}; //Bool to be non standart
public:
using element_type = T;
FancyPtr(std::nullptr_t n) : FancyPtr() {};
template<class S>
operator FancyPtr<S>() {
return {ptr};
}
T &operator*() { return *ptr; }
T &operator[](size_t n) { return ptr[n]; }
T *operator->() { return ptr; }
bool operator==(const FancyPtr &other) { return ptr == other.ptr; };
static FancyPtr pointer_to(element_type &r) { return FancyPtr(&r, false); };
};
TrivialAllocator.h:
#include "fancy_ptr.h"
template<typename T>
class TrivialAllocator {
public:
using pointer = FancyPtr<T>;
using value_type = T;
TrivialAllocator() = default;
template<typename Other>
TrivialAllocator(const TrivialAllocator<Other> &other) {};
template<typename Other>
TrivialAllocator(TrivialAllocator<Other> &&other) {};
TrivialAllocator(TrivialAllocator &alloc) = default;
pointer allocate(size_t n) { return pointer::pointer_to(*new T[n]); }
void deallocate(pointer ptr, size_t n) { delete[] &*ptr; };
bool operator==(const TrivialAllocator &rhs) const { return true; };
bool operator!=(const TrivialAllocator &rhs) const { return false; };
};
main.cpp:
#include "TrivialAllocator.h"
#include <list>
int main() {
struct Test {};
using AllocT = std::allocator_traits<TrivialAllocator<long double>>;
static_assert(std::is_same_v<FancyPtr<long double>,std::pointer_traits<AllocT::pointer>::pointer>);
static_assert(std::is_same_v<FancyPtr<Test>, std::pointer_traits<AllocT::pointer>::rebind<Test>>);
std::list<long double, AllocT::allocator_type> list;
}
The static assertions are ok.
Can anybody tell me what I have to to to get this working?
PS: I know that operator-> is in something like a conversion operator, but the underlying problem is that std::list seem not to save my fancy pointers, but raw pointers.
Upvotes: 1
Views: 750
Reputation: 40791
Your class does not fulfil all the requirements.
Your pointer type must be a Cpp17RandomAccessIterator (operator++, operator+=, operator+, operator--, operator-, operator-=, operator!=, operator<, operator>=, operator<=)
Your FancyPtr<void>
(which would be std::allocator_traits<TrivialAllocator<T>>::void_pointer
) does not compile because of the operator[]
and pointer_to(void&)
(void_pointer
and const_void_pointer
don't need to be random access iterators)
You can't convert from a pointer
to a void_pointer
(You need to change your conversion operator to return {static_cast<S*>(ptr);}
, or have the static_cast
on a constructor of void_pointer
)
You can't convert from your pointer type to bool
which is one of the requirements for NullablePointer.
Your allocators's allocate
and deallocate
shouldn't call constructors or destructors.
However, even after making it a valid pointer type for an allocator, you still run into issues with how libstdc++ and libc++ deals with pointers. At some points in the code, there is a cast from FancyPtr<ListNode>
to FancyPtr<ListNodeBase>
, where ListNode
derives from ListNodeBase
. This is not part of the requirements of a pointer type, but is used by the implementation of std::list
in those standard libraries anyways. You allow this by having operator FancyPtr<T>
for any T
, which is used. The standard libraries may be able to fix this by converting to a void pointer then to the base class, if the node classes are standard layout.
libstdc++ also internally uses raw T*
pointers everywhere, so there are points where it implicitly tries to convert from T*
to FancyPtr<T>
. Unfortunately, the only way to support this would be to have a public FancyPtr(T*)
constructor and a conversion to a raw pointer operator T*()
. libstdc++ could fix this without breaking ABI by using p ? std::pointer_traits<pointer>::pointer_to(*p) : pointer(nullptr)
to convert a T*
p
to a fancy pointer, and std::to_address
for the reverse.
Microsoft's STL's std::list
has no problems with a valid pointer type.
Here is a example implementation of a fancy pointer type that fulfils the requirements and has the workarounds for the 2 standard library implementations mentioned here: https://godbolt.org/z/vq9cvW
Upvotes: 2
Reputation: 26282
After some digging into the problem, I guess this is simply impossible due to libstdc++
internal limitations. This is a known old bug, "Node-based containers don't use allocator's pointer type internally":
Container nodes are linked together by built-in pointers, but they should use the allocator's pointer type instead. Currently I think only
std::vector
does this correctly. ...
It should work with Clang and libc++ (use -stdlib=libc++
command line option) with a couple of fixes:
FancyPtr<void>::pointer_to(...)
should be a valid member function. Now it is not, because void&
does not exist.FancyPtr
should provide operator!=(...)
member function.These fixes are needed to make your code at least compilable. Whether it will work correctly is out of scope of this answer.
Upvotes: 1