Open Quuxplusone opened 9 years ago
Attached string_offptr.cpp
(3685 bytes, text/x-c++src): Compilation failure of basic_string with fancy pointer allocator
(I think this bug is much harder than 20616. 20616 is about type erased allocators, and the class design is mostly good already, and there's only a small amount of interface detail that needs generalizing. This bug here affects the actual class design of the SSO implementation.)
Jonathan Wakely pointed out an error in my analysis: NullablePointers *may*
indeed be trivially constructible (as long as their null state can be obtained
by value initialization). However, they are not required to be trivially
constructible.
So the current libc++ string only works with trivially constructible fancy
pointers.
In particular, basic_string does not work with
boost::interprocess::offset_ptr<char> because offset_ptr's "NULL pointer"
representation is not all-bits-zero and thus offset_ptr cannot have a trivial
default constructor.
Likewise, basic_string does not work with any fancy pointer type which is not
trivially DEstructible, either. This could be made compilable-but-not-
necessarily-correct by giving "struct __rep" a non-defaulted destructor. IMO it
would be better in the short term to
static_assert(is_trivially_destructible_v<pointer>), so as to improve the user
experience and document libc++'s assumptions.
string:750:31: error: constructor for 'std::__1::basic_string<char,
std::__1::char_traits<char>, my::allocator<char> >' must explicitly initialize
the member '__r_' which does not have a default constructor
https://wandbox.org/permlink/gCyhJ8904LMfpD53
====
#include <string>
// #define FIRST_FAILURE
// #define SECOND_FAILURE
namespace my {
template<class T>
class nontrivial_ptr {
T *ptr;
public:
#ifdef FIRST_FAILURE
nontrivial_ptr() {} // deliberately non-trivial ctor (e.g. offset_ptr)
#else
nontrivial_ptr() = default;
#endif
nontrivial_ptr(T *p) : ptr(p) {}
operator T*() const { return ptr; }
nontrivial_ptr(nontrivial_ptr&&) = default;
nontrivial_ptr(const nontrivial_ptr&) = default;
nontrivial_ptr& operator=(nontrivial_ptr&&) = default;
nontrivial_ptr& operator=(const nontrivial_ptr&) = default;
#ifdef SECOND_FAILURE
~nontrivial_ptr() {} // deliberately non-trivial dtor (e.g. imperfect programming?)
#else
~nontrivial_ptr() = default;
#endif
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
operator bool() const { return ptr; }
};
template<class T>
class allocator : public std::allocator<T> {
using base = std::allocator<T>;
public:
using pointer = nontrivial_ptr<T>;
pointer allocate(size_t n) { return base::allocate(n); }
void deallocate(pointer p, size_t n) { return base::deallocate(p, n); }
};
} // namespace my
int main()
{
std::basic_string<char, std::char_traits<char>, my::allocator<char>> s;
}
string_offptr.cpp
(3685 bytes, text/x-c++src)