I'd like to be able to enforce at compile time that a particular type can be used only to create objects with automatic storage duration.
template<typename T, typename Alloc>
struct Array
{
T* data; // owned resource
Array(std::size_t size); // allocates via Alloc
~Array(); // deallocates via Alloc
};
typedef Array<int, AutoAllocator<int>> AutoArray;
void foo(AutoArray a) // ok
{
AutoArray l = AutoArray(); // ok
static AutoArray s; // error
new AutoArray(); // error
std::vector<AutoArray> v(1); // error
}
The application for this would be to enable choosing an optimal allocation strategy for resources owned by an instance of AutoArray. The idea being that the resource allocation pattern required for objects with automatic storage duration is compatible with a LIFO resource allocator.
What method could I use to achieve this in C++?
EDIT: The secondary goal is to allow the allocation strategy for Array to be transparently switched by dropping in either AutoAllocator or the default std::allocator.
typedef Array<int, std::allocator<int>> DynamicArray;
Assume that there is a large base of code that already uses DynamicArray.
This cannot be done. Consider that you created a type that held this as a member. When the compiler generates the code for the constructor of that type it does not know where the object is being created, is the complete object in the stack, is it in the heap?
You need to solve your problem with a different mind set, for example, you can pass the allocator to the constructor of the object (the way BSL does) and possibly default to a safe allocator (based on new-delete), then for those use cases where a lifo allocator is a better option the user can explicitly request it.
This won't be the same as a compiler error, but it will be obvious enough to detect on a code review.
If you are really interested on interesting uses of allocators, you might want to take a look at the BSL replacement for the standard library, as it allows for polymorphic allocators that are propagated to the members of containers. In the BSL world, your examples would become:
// Assume a blsma::Allocator implementing LIFO, Type uses that protocol
LifoAllocator alloc; // implements the bslma::Allocator protocol
Type l(&alloc); // by convention bslma::Allocator by pointer
static Type s; // defaults to new-delete if not passed
new (&alloc) Type(&alloc); // both 'Type' and it's contents share the allocator
// if the lifetime makes sense, if not:
new Type; // not all objects need to use the same allocator
bsl::vector<Type> v(&alloc);
v.resize(1); // nested object uses the allocator in the container
Using allocators in general is not simple, and you will have to be careful of the relative lifetimes of the objects with respect to each other and to the allocators.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With