On Sunday, 30 April 2017 at 21:43:26 UTC, Andrei Alexandrescu wrote:
On 04/27/2017 07:35 PM, Stanislav Blinov wrote:
IAllocator is too high level an interface, it doesn't carry any
information as to what type of memory it can allocate (so we can only assume unshared), and does or does it not use GC (so we can only assume
GC).

Initially all fresh memory is unshared. Whether or not the user subsequently shares it is of no consequence to the allocator.

Why would we need any ISharedAllocator then? If we're to force users to rely on *documentation* whether or not they can cast allocated memory to shared, there is no point in static checks, they'll only get in the way of user's attempts to try and stitch together the pieces. They're on their own anyway at that point.

If we are to devise types with allocators as members instead of type arguments, we need the additional information. Better to catch invalid assignment at compile time than to chase down how a stack allocator from
one thread ended up in another.

A pass through the root allocators (Mallocator, GCAllocator etc) figuring out what attributes could be meaningfully attached would be welcome. The rest would rely on inference.

If we're type-erasing allocators, we have to have the ability to statically specify what traits we're interested in (i.e. *don't* want to erase). Otherwise, inference will not be of any help. Consider an example, inspired by the discussion of Atila's automem library:

import std.experimental.allocator;
import std.algorithm.mutation : move;

// Allocator is not a type parameter
struct SmartPtr(T)
{
    // we can infer all we need form A...
    this(A, Args...)(A alloc, auto ref Args args)
    if (isAllocatorImpl!A)
    {
        // ...but we immediately throw the inference away:
        allocator_ = alloc;
        block_ = cast(Block[])allocator_.allocate(Block.sizeof);
        // SmartPtr logic snipped...
    }

    ~this()
    {
        // ...SmartPtr logic snipped
        allocator_.deallocate(block_);
    }

private:
    struct Block
    {
        size_t refCount;
        void[T.sizeof] payload;
    }

    Block[] block_;
    IAllocator allocator_;
}

struct Data {
    ~this() @nogc { /*...*/ }
    // the rest of implementation is @nogc as well
}

struct MyType
{
    // won't compile: IAllocator's methods aren't @nogc,
    // so SmartPtr's dtor isn't either
    this(SmartPtr!Data data) @nogc
    {
        data_ = move(data);
    }

private:
    SmartPtr!Data data_;
}

Obviously, IAllocator is not the tool for the job here. This calls for something like this:

enum AllocTraits
{
    none  = 0x00,
    nogc  = 0x01,
    share = 0x02
}

alias AllocatorInterface(AllocTraits) = // ???

struct SmartPtr(T, AllocTraits traits = AllocTraits.none)
{
    this(A, Args...)(A alloc, auto ref Args args)
    {
        // this should not compile if A isn't compatible
        // with `traits`:
        allocator_ = alloc;
        block_ = allocator_.allocate(T.sizeof);
        // SmartPtr logic snipped...
    }

    ~this()
    {
        // ...SmartPtr logic snipped
        allocator_.deallocate(block_);
    }

private:
    void[] block_;
    AllocatorInterface!AllocTraits allocator_;
}

alias DataPtr = SmartPtr!(Data, AllocTraits.nogc);

struct MyType
{
    this(DataPtr data) @nogc
    {
        data_ = move(data);
    }

private:
    DataPtr data_;
}

That *would* be able to rely on inference. Question is, what is AllocatorInterface? Should we push such type erasure to the users?

Reply via email to