The constructors of std::any that create an object of type T use std::allocator<T> to allocate memory and "::new ((T*)p)" placement new to construct the object. (That is, memory allocation subject only to specializations of std::allocator, and construction only to overloads of global operator new for user pointer types.) By contrast, destruction uses "delete (T*)p". This means that overloaded operators T::operator delete will be used. (In the above, the cast notation "(T*)" doesn't appear literally in the code, but is just my way of indicating the argument type. Specifically, note that there is no cast to void* in the placement new.) This mismatch results in undefined behaviour whenever a suitable amount of user customization is present for T. Examples can be constructed to exercise all aspects of this (specializing std::allocator, adding a ::operator new(std::size_t, T*) overload, or adding overloaded operators T::operator delete). One example: ================ #include <any> #include <cstddef> #include <cstdlib> struct U { int big[128]; static void* operator new(std::size_t n) { return std::malloc(n); } static void operator delete(void* p) { std::free(p); } }; int main() { std::any a(std::in_place_type<U>); return std::any_cast<U*>(a) != nullptr; // avoid optimizing everything away } ================ The generated code clearly shows one call to "operator new" and one to "free". One solution would be to make the construction use an unqualified new expression "new _Tp(...)"; this means that standard library code will call user-defined functions for internal bookkeeping. An alternative solution would be to also use std::allocator to deallocate the allocation.
There's more to this than the reported issue. The any implementation uses placement new to construct the objects. This should really be allocator_traits<allocator<T>>::construct(...). And T::~T should be allocator_traits<allocator<T>>::destroy(...). But yes, the issue as reported is correct.
reading up on this some more, I note that (https://wg21.link/allocator.members): std::allocator<T>::allocate is required to get its storage from `::operator new`, and std::allocator<T>::delete is required to use `::operator delete` but there's certainly some wiggle room in those specifications. (Deliberately, I'm sure)
(In reply to Marshall Clow (home) from comment #2) > reading up on this some more, I note that > (https://wg21.link/allocator.members): > > std::allocator<T>::allocate is required to get its storage from `::operator > new`, and > std::allocator<T>::delete is required to use `::operator delete` > > but there's certainly some wiggle room in those specifications. > (Deliberately, I'm sure) Yes, this is true, but the way I read this is merely as a guarantee that, say, replacements of "::operator new" are observed by (any specialization of) std::allocator. It doesn't preclude specializations from doing additional, custom work (so the allocator should still be called in matching pairs, and not freely interchanged with direct calls to "::operator new/delete").
Took over potential solution in https://reviews.llvm.org/D81133.
Fixed by 39c8795141703a7d8313b2448d9d34e856df0b85