On 05/27/2013 06:55 PM, Joseph Rushton Wakeling wrote:

> On 05/27/2013 11:33 PM, Simen Kjaeraas wrote:

>> Short answer: If you will have mixed arrays, no. There's no way to make
>> that safe. If you don't have mixed arrays, there are ways.
>
> So you mean there's no way to have one member variable be immutable, the rest
> mutable, without hardcoding that into the class/struct design?

That is a difficult situation to manage though: What operations are valid under the 8 total mutable combinations of 3 members? The compiler must know what operations to be applied on what type of data so that it can both check the code and compile it accordingly.

void foo(MyDataStore my)
{
    my.someData[0] = 1.5;    // valid if someData is mutable
    my.someMoreData[0] = 42; // valid if someMoreData is mutable
}

Clearly, the code above can be compiled only if the two members of MyDataStore are mutable. The compiler does not have anything other than the static definition of MyDataStore, which implies that the type qualifiers of the members must be known at compile time.

To have such a flexibility, the type must be templatized. When it is templatized though, different instantiations of the template are different types, which means that they cannot take part in a collection of a specific type, unless they implement a certain interface like DataStore below:

import std.typecons;

interface DataStore
{
    void doWork();
}

class MyDataStoreImpl(SomeDataT,
                      SomeMoreDataT,
                      ImportedDataT) : DataStore
{
    SomeDataT[] someData;
    SomeMoreDataT[] someMoreData;
    ImportedDataT[][] importedData;

    this(SomeDataT[] sd, SomeMoreDataT[] smd, ImportedDataT[][] id)
    {
        someData = sd;
        someMoreData = smd;
        importedData = id;
    }

    void doWork()
    {
        // What can we do here?
        // There are 8 combinations of type qualifiers.
    }
}

auto makeMyDataStore(SomeDataT,
                     SomeMoreDataT,
                     ImportedDataT)(SomeDataT[] sd,
                                    SomeMoreDataT[] smd,
                                    ImportedDataT[][] id)
{
    return new MyDataStoreImpl!(SomeDataT,
                                SomeMoreDataT,
                                ImportedDataT)(sd, smd, id);
}

void foo(DataStore[] store)
{
    foreach (data; store) {
        data.doWork();
    }
}

void main()
{
    DataStore[] store;

    // The combination: mutable, const, immutable
    store ~= makeMyDataStore(new float[1],
                             new const(double)[3],
                             [ [ immutable(Tuple!(size_t, size_t))() ] ]);

    // Another combination: immutable, mutable, const
    store ~= makeMyDataStore(new immutable(float)[2],
                             new double[4],
                             [ [ const(Tuple!(size_t, size_t))() ] ]);

    foo(store);
}

There is the big question of how to implement MyDataStoreImpl.doWork(). How do we support every valid combination?

Conditional compilation is one way:

class MyDataStoreImpl(SomeDataT,
                      SomeMoreDataT,
                      ImportedDataT) : DataStore
{
// ...

    void doWork()
    {
        static if (is (SomeMoreDataT == const) &&
                   is (ImportedDataT == immutable)) {
            // ...
            writeln("case 1");

        } else static if(is (SomeDataT == immutable) &&
                         is (ImportedDataT == const)) {
            // ...
            writeln("case 2");

        } else {
           // ...
        }
    }
}

Perhaps mixins can be used to inject different functionality depending on different type qualifiers if the functionality is as simple as in the following case:

import std.stdio;

// This template contains code for a mutable slice and the function that goes
// with it:
template Foo(T)
    if (!is (T == immutable) &&
        !is (T == const))
{
    T data[];

    void doWork()
    {
        writeln("called for mutable data");

        if (data.length < 1) {
            data.length = 1;
        }

        data[0] = T.init;
    }
}

// This one is for immutable and const data:
template Foo(T)
    if (is (T == immutable) ||
        is (T == const))
{
    T[] data;

    void doWork()
    {
        // We cannot modify data[0] here...
        writeln("called for immutable data");
    }
}

interface DataStore
{
    void doWork();
}

class MyDataStoreImpl(T) : DataStore
{
    mixin Foo!T;
}

void main()
{
    DataStore[] store;

    store ~= new MyDataStoreImpl!(double)();
    store ~= new MyDataStoreImpl!(immutable(double))();

    foreach (data; store) {
        data.doWork();
    }
}

Ali

Reply via email to