Passing large or complex data structures to threads
Ali Çehreli
acehreli at yahoo.com
Mon May 27 23:56:12 PDT 2013
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
More information about the Digitalmars-d-learn
mailing list