EASTL
EASTL copied to clipboard
eastl::list::splice using copy operator when it shouldn't
I'm using eastl to build my own engine and I'm surprised that something as simple as splice it calling a copy operator. This causing both performance problem and annoying architecture problem for me (especially when I want to use stuff like Atomic<>)
here is a test code to isolate the problem:
#include <list>
#include <EASTL/list.h>
class C {
public:
C() = default;
C(const C&) = delete;
C(C&&) = delete;
};
#define useEA
#ifdef useEA
typedef eastl::list<C> list;
#else
typedef std::list<C> list;
#endif
int main() {
list l0;
l0.emplace_back();
l0.emplace_back();
list l1;
l1.emplace_back();
l1.emplace_back();
l0.splice(l0.end(), l1, l1.begin());
}
if you comment the line #define useEA
it will compile properly without any problem but if you keep it you will get something like:
include/EASTL/list.h:2003:34: error: call to deleted constructor of 'eastl::list<C, eastl::allocator>::value_type' (aka 'C')
::new((void*)&pNode->mValue) value_type(eastl::forward<Args>(args)...);
/include/EASTL/list.h:1478:28: note: in instantiation of function template specialization 'eastl::list<C, eastl::allocator>::DoCreateNode<const C &>' requested here
node_type* const pNode = DoCreateNode(value);
include/EASTL/list.h:1669:4: note: in instantiation of member function 'eastl::list<C, eastl::allocator>::insert' requested here
insert(position, *i);
(using clang++ with -std=c++14) According to the original STL: https://en.cppreference.com/w/cpp/container/list/splice "No elements are copied or moved, only the internal pointers of the list nodes are re-pointed" which is definitely super useful when you want to move heavy object or having conditions where it's not a good idea to copy.
inline void list<T, Allocator>::splice(const_iterator position, list& x, const_iterator i)
{
if(mAllocator == x.mAllocator)
{
iterator i2(i.mpNode);
++i2;
if((position != i) && (position != i2))
{
((ListNodeBase*)position.mpNode)->splice((ListNodeBase*)i.mpNode, (ListNodeBase*)i2.mpNode);
#if EASTL_LIST_SIZE_CACHE
++mSize;
--x.mSize;
#endif
}
}
else
{
insert(position, *i);
x.erase(i);
}
}
The issue here is eastl::list has implemented a fallback mechanism for when the list allocators are not equal. The fallback copies the values so for types that aren't copyable or movable this is a problem. Really the only way to solve this bug is to deprecate the fallback mechanism and leave it as UB as the standard outlines.
"No elements are copied or moved, only the internal pointers of the list nodes are re-pointed. The behavior is undefined if: get_allocator() != other.get_allocator()."
Generally the main problem here is compilation (both in splice and swap, I guess). Even if you don't use it, code will just not compile because of existence of fallback. I guess, we can make it compile-time definable, or use some kind of traits/template parameter to avoid broken compilation, if fallback is really needed. Alternatively, fallback can be replaced with assert (making it explicit run-time UB).
AFAIR, stl allows correct swap/splice, if allocators can propagate using traits, which is probably not a case for EASTL.