5 ms·
Before I give an example, note a couple of things: - You've picked some of the best C++ libraries as if they're somehow representative of the ocean of C++ code
by dataflow 6y ago
Before I give an example, note a couple of things:
- You've picked some of the best C++ libraries as if they're somehow representative of the ocean of C++ code that's out there, whereas I was talking more about the general landscape.
- It's hard to do an apples-to-apples comparison for a library (like Abseil) that tries to avoid replicating what's already in the standard library, so those aren't necessarily the best examples to discuss here.
That said, OK, here's one trivial example. It is a major time sink to dig these up and write self-contained examples for the sake of argument (it took me 1 hour to write this entire comment), so I hope this example can be sufficient to get my point across.
So folly has FBVector and it supports custom allocators, right? OK, so just try making a vector with a custom pointer type:
#include <vector>
// #include <folly/FBVector.h>
template<class T>
struct my_pointer
{
typedef my_pointer this_type;
typedef T element_type, value_type;
typedef ptrdiff_t difference_type;
typedef this_type pointer;
typedef value_type &reference;
bool operator!=(this_type const &other) const { return !(*this == other); }
bool operator==(this_type const &other) const { return this->p == other.p; }
difference_type operator-(this_type const &other) const { return this->p - other.p; }
explicit operator bool() const { return !!this->p; }
friend this_type operator +(difference_type n, this_type me) { return me + n; }
explicit my_pointer(T *p) : p(p) { }
my_pointer(std::nullptr_t p) : p(p) { }
my_pointer() : p() { }
reference operator *() const { return *this->p; }
template<class U> using rebind = my_pointer<T>;
this_type &operator++() { ++this->p; return *this; }
this_type &operator+=(difference_type n) { this->p += n; return *this; }
this_type &operator--() { --this->p; return *this; }
this_type &operator-=(difference_type n) { this->p -= n; return *this; }
this_type operator+(difference_type d) const { return this_type(this->p + d); }
this_type operator++(int) { this_type copy(*this); ++*this; return copy; }
this_type operator-(difference_type d) const { return this_type(this->p - d); }
this_type operator--(int) { this_type copy(*this); --*this; return copy; }
value_type *operator->() const { return this->p; }
reference operator[](difference_type d) { return this->p[d]; }
private:
T *p;
};
template<class T>
struct my_allocator
{
std::allocator<T> base;
typedef T value_type;
typedef size_t size_type;
typedef my_pointer<T> pointer;
template<class U> struct rebind { typedef my_allocator<U> other; };
pointer allocate(size_type n) { return pointer(this->base.allocate(n)); }
void deallocate(pointer p, size_type n) { return this->base.deallocate(&*p, n); }
};
namespace std
{
template<class T>
struct iterator_traits<my_pointer<T> >
{
typedef typename my_pointer<T>::pointer pointer;
typedef typename my_pointer<T>::reference reference;
typedef typename my_pointer<T>::value_type value_type;
typedef typename my_pointer<T>::difference_type difference_type;
typedef std::random_access_iterator_tag iterator_category;
};
}
int main()
{
#ifdef FOLLY_CPLUSPLUS
folly::fbvector
#else
std::vector
#endif
<int, my_allocator<int> > numbers;
numbers.push_back(1);
return 0;
}
This compiles and runs fine with GCC, Clang, and MSVC (on, say, C++17).
But now try to uncomment the #include so that it uses folly and you suddenly get errors like this:
/usr/include/folly/FBVector.h:148:27: error: no viable conversion from 'folly::fbvector<int, my_allocator<int>>::Impl::pointer' (aka 'my_pointer<int>') to 'int *'
S_destroy_range(b_, e_);
/usr/include/folly/FBVector.h:365:34: note: passing argument to parameter 'first' here
static void S_destroy_range(T* first, T* last) noexcept {
Funny, so S_destroy_range is used internally, and it requires raw pointers. But who says my fancy pointers will even necessarily map 1:1 to a linear (and contiguous!) pointer space?
What we see here is folly pretends to support custom allocators, but it cuts corners internally. (!) Which is awful not only because of the inflexibility, but because it misleads you, too. If they're introducing unfounded assumptions internally just out of sheer convenience, how am I supposed to trust the implementation? Heck, if I had implemented implicit conversions to raw pointers so that the code compiled, I might not have even discovered there's a latent bug in my program.
In contrast, in my experience, actual standard library implementations pay attention to the details and don't tend to cut corners like third party libraries do.
Now, again, keep in mind this is what we get with some of the best libraries, whereas I was talking about the general landscape, so the situation isn't even remotely this good on average.
- Kranar 6y agoI do appreciate the effort you went to for this, but you make a lot of very strong claims that depend on very obscure minutia and unfortunately they end up being false under careful scrutiny. >But who says my fancy pointers will even necessarily map 1:1 to a linear (and contiguous!) pointer space? The standard as of C++11 does. std::vector<T> provides the member function T* data() which is required to return a pointer to the first element of a contiguous memory region spanning the entire vector: https://en.cppreference.com/w/cpp/container/vector/data https://en.cppreference.com/w/cpp/container/vector/data Note that it specifically returns a T* rather than a my_pointer<T>, or what is referred to in standardese as a "fancy pointer": https://en.cppreference.com/w/cpp/named_req/Allocator#Fancy_pointers https://en.cppreference.com/w/cpp/named_req/Allocator#Fancy_... >What we see here is folly pretends to support custom allocators, but it cuts corners internally. No such corners are cut. Folly has full support for custom allocators including fancy pointers and in this case it's giving you a compile time error for what could have been undefined behavior. That's an advantage in my book, I'll take compile time error over hard to debug memory corruption any day of the week. Fancy pointers to T are required to be implicitly convertible to T* and while the small snippet of code you pasted doesn't make use of this requirement in GCC and clang, MSVC does make use of this requirement and you can see as per the link below that your allocator fails to work: https://godbolt.org/z/7KrE41 https://godbolt.org/z/7KrE41 You'll notice the error is that std::vector is trying to convert your fancy pointer into a raw pointer in order to call _Delete_plain_internal: >C:/data/msvc/14.28.29910/include\vector(683): error C2893: Failed to specialize function template 'void std::_Delete_plain_internal(_Alloc &,_Alloc::value_type *const ) noexcept' Other parts of the standard that depend on this are node based containers such as std::list, std::set, and std::allocate_shared also makes use of this requirement. The fact is that writing a standard conforming allocator that uses fancy pointers is incredibly difficult and error prone, the standard is not clear about the rules with numerous defect reports related to fancy pointers and as of today no compiler actually has full support for it: https://quuxplusone.github.io/draft/fancy-pointers.html https://quuxplusone.github.io/draft/fancy-pointers.html The fact that you could have picked any example to prove your point about the standard library and the one you chose to produce took you a very long time to exercise what is a fringe and corner case example of a defective language feature that you do not fully understand is not a particularly convincing argument that the standard library provides the high quality implementations and APIs. That may seem blunt and harsh, but you're in good company since I myself also don't fully understand it, and anyone who's being honest would also admit that fancy pointers in C++ are an area that quite possibly no one really truly understands. Before jumping to the conclusion that Facebook got it wrong, they cut corners, and their code is not trustworthy over this, perhaps it might be best to pause a moment; this issue has nothing to do with Facebook and instead is merely a reflection of the sheer complexity of C++ as a language.