• Keine Ergebnisse gefunden

In this section, we would like to lay out our approach to the solution of the constructor problem in mixin based programming.

That there lies a problem, was first mentioned in [SB00]: If a type is constructed with various mixins, it is not a priori clear, how many and which parameters the constructor in a specific mixin class has to take in its definition. Writing constructors for each possible combination of mixins e.g.

would increase the code basis more than exponentially.

A solution technique was proposed in [EBC00], using generic parameter classes (implemented as type lists),configuration repositories andparameter adapters. However it still needs a specific order of the parameters in the constructor, which is not evident from the DSL that the generator is provided with. Furthermore it needs configuration structures for every combination of mixins.

We use a different approach with aggregated parameter data structures that are used internally.

Parameters for a constructor are then specified in a special call convention, see Sections 3.4.3 and 3.4.4.

3.4.1 The parameter aggregate

The purpose for the Dataaggregate is to store the parameters for the construction of a layered type.

The aggregate itself is assembled out of small and simple layers that have accessor functions to get and set variables.

A specific data of typeT, associated with the tagDataName, is stored in the aggregate using the setValue(DataName m, const T& t )

function. The according data is retrieved using the

getValue(DataName m) member function.

Function calls concerning data that is not associated with theDataNametag are dispatched to the aggregated object bof typeBase.

Data aggregate template <class Base,

class DataName, class T,

class SizeType,

class MemoryClass=Value>

class Data {

public:

typedef Base BaseType;

typedef T StoredType;

typedef DataName NameType;

typedef StoredType MemoryType;

typedef SizeType size_type;

typedef MemoryClass StorageType;

private:

BaseType b;

size_type size;

NameDataTupel<NameType,T> t;

public:

Data() : size(0) {}

Data(const Data& d) : b(d.b), size(d.size), t(d.t) {}

Data(const T& t_, const Base& b_) : b(b_), size(0), t(t_) {}

void setSize(NameType m, size_type size_) {

size = size_;

}

template <class OtherName, class MemberSizeType>

void setSize(OtherName m, MemberSizeType size_) {

b.setSize(m,size_);

}

size_type getSize(NameType m) const

{

return size;

}

const T& getValue(NameType m) {

return t;

}

void setValue(NameType m, const StoredType& t_) {

t = t_;

}

template <class MemberNameType, class MemberStoredType>

void setValue(MemberNameType m, const MemberStoredType& t_) {

b.setValue(m, t_);

} };

The dispatch for the retrieval of data is a bit more tricky than the setting of the data, since in this case the return type for this function is not known apriori in the current layer. In order to get the correct return type, we have to make use of a template metaprogramming mechanism which we call stored type lookup, cf. Section 3.5. The according member functions to retrieve data that is stored in another layer are described there.

This data aggregate can now be arbitrarily assembled to any size and with any type. We need an emtpy type at the top to terminate the structure and to enable StoredTypeLookupto determine whether a type is contained in the structure or not.

Empty type struct EmptyType {};

We also have a specialization for storing pointers, which is especially useful when used as a pa-rameter structure for the construction of objects, since it is cheaper in most cases to copy a pointer than to copy a whole data structure.

At last, we also havegetSequenceandsetSequencemember functions. These can be used instead of getValue, if the according layer needs to store a sequence.

3.4.2 A simple construction mechanism

As we have seen in Section 3.3.4, every layer that stores data has a definition of a ParameterType.

An object of a concrete matrix type, e.g. named MatrixType, can now be constructed like in the following example.

Object construction example 1 MatrixType::ParameterType parameter;

parameter.setValue(HorizontalDimension(),5000);

parameter.setValue(VerticalDimension(),10000);

parameter.setSize(NeighbourhoodStarts(),10001);

parameter.setSize(EdgeTargets(),100000);

parameter.setSize(WeightValues(),100000);

MatrixType A(parameter);

The above example constructs a rectangular 10000×5000 sparse matrix with free space for 100000 nonzero entries, by setting the values of the dimensions and the sizes of the according sequences.

In the next example, we can see, that it is possible through thesetSequencemember functions of Data, to import data out of existing sequences.

Object construction example 2 DataSequence<size_t,size_t> weights(1000);

DataSequence<size_t,size_t> targets(1000);

DataSequence<double,size_t> starts(101);

MatrixType::ParameterType parameter;

parameter.setValue(HorizontalDimension(),100);

parameter.setValue(VerticalDimension(),100);

parameter.setSequence(NeighbourhoodStarts(),starts);

parameter.setSequence(EdgeTargets(),targets);

parameter.setSequence(WeightValues(),weights);

MatrixType B(parameter);

However this way of constructing an object is clumsy and cumbersome for the user of the library.

Instantiating a parameter object, initializing it and then creating the actual object is not what we are used in object-oriented programming. In the following, we present an advanced solution which combines aspects from [EBC00] with the above data aggregate.

3.4.3 Parameter constructor and adapter classes

Our intention is to supply a construction interface for objects of arbitrary type that allows to specify the initial data in different ways and in any order.

We prescribe the following format for constructor arguments. Every parameter in a constructor call has to be specified as follows:

• a name tag indicating the layer,

• a construction tag specifying the way the data is constructed in the end,

• the parameter itself.

More precisely, the name tag (e.g. HorizontalDimension()) acts as an instruction, where to insert the data in the layer hierarchy, and therefore declares the role the parameter plays in the whole data structure. The construction tag, which can be one of the types Size,Value,Sequence indicates the way the object in the MemberLayer is constructed. UsingValue simply copies the parameter object.

For sequences, we have the choice of constructing the member by using the size constructor, or, if we have an already constructed sequence, than we indicate with the tag Sequence, that we would like to use this extern source of data. The sequence is then either copied or a handle (pointer) to this sequence is stored, depending on how the type was assembled.

This concept is realized with a parameter constructor class and a parameter adapter class, that have generic constructors for up to n parameters (t1,. . . ,tn) given by a total of 3narguments.

Parameter constructor class template <class MatrixType>

class ParameterConstructor {

public:

typename MatrixType::DataType p;

ParameterConstructor() {}

template <class Name1, class C1, class T1>

ParameterConstructor(const Name1& n1, const C1& c1, const T1& t1) {

p.setMember(n1,c1,t1);

}

template <class Name1, class C1, class T1, class Name2, class C2, class T2>

ParameterConstructor(const Name1& n1, const C1& c1, const T1& t1, const Name2& n2, const C2& c2, const T2& t2) {

p.setMember(n1,c1,t1);

p.setMember(n2,c2,t2);

}

template <class Name1, class C1, class T1, class Name2, class C2, class T2, class Name3, class C3, class T3>

ParameterConstructor(const Name1& n1, const C1& c1, const T1& t1, const Name2& n2, const C2& c2, const T2& t2, const Name3& n3, const C3& c3, const T3& t3) {

p.setMember(n1,c1,t1);

p.setMember(n2,c2,t2);

p.setMember(n3,c3,t3);

} //...

};

A drawback of this approach is that we have to supplyntemplatized constructors. However these constructors only vary in the number of arguments and can therefore be implemented using macros or other automatic code generation tools.

Parameter adapter class template <class MatrixType>

class ParameterAdapter : public MatrixType {

typedef ParameterConstructor<MatrixType> Parameter;

public:

template <class Name1, class C1, class T1>

ParameterAdapter(const Name1& n1, const C1& c1, const T1& t1) : MatrixType(Parameter(n1,c1,t1))

{}

template <class Name1, class C1, class T1, class Name2, class C2, class T2>

ParameterAdapter(const Name1& n1, const C1& c1, const T1& t1, const Name2& n2, const C2& c2, const T2& t2) : MatrixType(Parameter(n1,c1,t1,n2,c2,t2))

{}

template <class Name1, class C1, class T1,

class Name2, class C2, class T2, class Name3, class C3, class T3>

ParameterAdapter(const Name1& n1, const C1& c1, const T1& t1, const Name2& n2, const C2& c2, const T2& t2, const Name3& n3, const C3& c3, const T3& t3) : MatrixType(Parameter(n1,c1,t1,n2,c2,t2,n3,c3,t3))

{}

//...

};

An existing type Typecan now be decorated with these constructors by defining e.g. MatrixTypeas ParameterAdapter<MatrixType>.

3.4.4 Advanced construction mechanism

Now we are able to construct the objects from Section 3.4.2 in the following way:

Object construction example 3 MatrixType A(HorizontalDimension(),Value(),5000,

VerticalDimension(),Value(),10000, NeighbourhoodStarts(),Size(),10001, EdgeTargets(),Size(),100000,

WeightValues(),Size(),100000);

Existing data can be used like this:

Object construction example 4 DataSequence<size_t,size_t> weights(1000);

DataSequence<size_t,size_t> targets(1000);

DataSequence<double,size_t> starts(101);

MatrixType B(HorizontalDimension(),Value(),100, VerticalDimension(),Value(),100,

NeighbourhoodStarts(),Sequence(),starts, EdgeTargets(),Sequence(),targets,

WeightValues(),Sequence(),weights);