design - class to represent a variable

B

bartek

Hello,

I've been pondering with this for quite some time now, and finally
decided to ask here for suggestions. I'm kind of confused, actually...
Maybe I'm thinking too much...

Brain dump follows...

I need a class to represent a variable, with an associated data type
and/or value. Though, I don't want it to be a variant type, and not a
'convenience' class either.

The variable objects will be used in variable lists (a'la symbol tables),
so they are going to be polymorphic.

Now to the point...

The variable type is determined by the following factors:
1. raw data type (int, float, string, etc...)
2. array width (if it's an array of p.1)
3. storage class modifier

My dilemma concerns the actual design of the way how to access those
variables in different parts of the system.

The lists of variables are used in quite different situations.
For example, in generating function prototypes (type declarations),
carrying actual data around the system, translating data coming from
external sources.

The above requirements are pushing me towards a consequent use of the
visitor pattern. Though, I'm completely puzzled on how to apply visitor
to a type which is determined by 3 factors (as shown above).

It all seems as a total maintenance disaster at the moment...

THE Question:
Is there a simple way (or a way to simplify) such a case of three-way-
visitation?

Should I go for explicit definition of each type?

E.g.

Given types Int and Float, and storage classes Uniform and Varying,
define every possible combination of data type, array specifier, and
storage class modifier. ouch!

class Variable ... // the root

class UniformInt : public Variable ...
class UniformIntArray : public Variable ...
class VaryingInt : public Variable ...
class VaryingIntArray : public Variable ...
class UniformFloat : public Variable ...
etc...

Maybe I could nest it in some way?

E.g.

class StorageSpec ... // root

class Uniform : public StorageSpec ...
class Varying : public StorageSpec ...

class Variable ... // root

class Float : public Variable {
StorageSpec* m_storage_spec;
};
etc...

Or maybe the other way around, that is - embed data type in a storage
class?

I'm aware that it's impossible to help me with so small amount of
information given. Though I hope you can give me some references to
whatever medium, where a remotely similar problem is solved.

Thanks for your time.

Cheers.
 
D

Derek

What exactly are you writing? I'm curious because I'm
working on a very similar problem.
 
B

bartek

Derek said:
What exactly are you writing? I'm curious because I'm
working on a very similar problem.

Actually it's going to be a part of an application utilizing Renderman
interface. I'd like to represent RI variable specifications in C++ classes
following consequent OO practices. *cough* *cough*
 
D

Derek

What exactly are you writing? I'm curious because I'm
Actually it's going to be a part of an application
utilizing Renderman interface. I'd like to represent
RI variable specifications in C++ classes following
consequent OO practices. *cough* *cough*

That's what I thought. I just started a little RenderMan
project myself (yet another RenderMan-compliant renderer).
It's very new and I can barely render a sphere at this
point, but I've been thinking ahead about how to implement
RI variables.

I've started peeking at open-source renderers to see how
they handle variables (Aqsis and Pixe are both C++), but I
haven't come up with a definitive design yet.

Let me know if you come up with a cool design. My email
address is "derek AT networld DOT com" if you want to
compare notes sometime.
 
D

Derek

I'm not sure you want to deal with the combinatorial
explosion of using the visitor pattern. After all,
there are 5 storage classes (constant, uniform,
varying, facevarying, and vertex) and 10 types
(float, integer, vector, color, normal, point, matrix,
double, hpoint, and string). And each combination
can be a scalar or an array. Yikes!

I propose a very simple strucutre (off the top of my
head):

class Variable
{
public:
//...
private:
std::string m_varName;
StorageType m_storageType;
DataType m_dataType;
int m_arrayLength;
int m_elementLength;
std::vector<float> m_data;
};

Now your algorithms can 'switch' based on type type if
they have to. I think you will find that many operations
(e.g., linear interpolation) don't care about the type
at all.

Perhaps the only wrinkle is that string variables can't
be stored very efficiently in a vector<float> and should
probably be treated separately.
 
B

bartek

Derek said:
I'm not sure you want to deal with the combinatorial
explosion of using the visitor pattern. After all,
there are 5 storage classes (constant, uniform,
varying, facevarying, and vertex) and 10 types
(float, integer, vector, color, normal, point, matrix,
double, hpoint, and string). And each combination
can be a scalar or an array. Yikes!

That's exactly the point.
Having such huge visitors seemed like a bad idea.
On the other hand, visitor guarantees that every subtype will be serviced
individually in the code.
I propose a very simple strucutre (off the top of my
head):

class Variable
{
public:
//...
private:
std::string m_varName;
StorageType m_storageType;
DataType m_dataType;
int m_arrayLength;
int m_elementLength;
std::vector<float> m_data;
};

Now your algorithms can 'switch' based on type type if
they have to. I think you will find that many operations
(e.g., linear interpolation) don't care about the type
at all.

I've been considering a similar design, except that the main data was
separated into respective derived classes.

That is:

class Variable {
std::string m_name;
StorageType m_storage_type;
int m_size; // ==0 : scalar, >0 - array
public:
virtual void AcceptVisitor(Visitor&) const = 0;
// ... accessors, etc.
};

class FloatVariable : public Variable {
std::vector<float> m_data;
public:
//...
};

class StringVariable : public Variable {
std::vector<std::string> m_data;
public:
//...
};

// etc...

This way, I could dispatch based on the actual data type, and use
conditionals for the storage type and/or array specification.

But then ... I'd probably end up with such conditionals in every visitor,
which is kind of contradictory to the whole design. Why bother with
visitor at all when there will be switches everywhere, anyway?

On the other hand, considering the fact that the storage specifier is
only giving a new meaning for array dimensions, I thought about turning
it into a kind of templated policy class, which would only determine the
algorithms for operations on the data regardless of the type...

E.g.

// only one element to copy
struct UniformStorage {
template <class IterT>
static void Copy(IterT to, IterT from)
{ *to = *from; } // just one value to copy here
// ...
};

// nvertices elements to copy
class VaryingStorage {
int m_nvertices;
public:
VaryingStorage(int nvertices) : m_nvertices(nvertices) { }
template <class IterT>
static void Copy(IterT to, IterT from) {
for (int i=0; i!=m_nvertices; ++i) {
*to = *from;
++to; ++from;
}
}
};

And then use those policies as template arguments for FloatVariable et
al.

This technique, however, rules out the GOF style visitation, because
virtual functions cannot be templates.
Unless all templates are given explicitly...

// in visitor
virtual void Visit(FloatVariable<UniformStorage> const&)
//...

So here we go back to the huge-visitor maintenance problem, because every
possible combination of variable with storage class must be given
explicitly...

Uhh too much coffee I guess...

Cheers
 
D

Derek

Your logic is all too familiar; I've gone 'round and
'round with the same thoughts. The biggest problem is
that I'm not really sure what operations I will need
to perform on RI variables. That is, I'm not convinced
that I will really have to switch based on type in all
THAT many places. I really think in many cases all
numerical data can be treated uniformly regardless of
type. In that case a simple one-class solution with a
few switch statements here and there is probably the
best way to go. If I'm wrong, however, and I need to
switch all over the place, I will revisit a polymorphic
solution with visituation. Either way, I'm still a few
weeks away from having to worry about it. Good luck.
 
B

bartek

Derek said:
Your logic is all too familiar; I've gone 'round and
'round with the same thoughts. The biggest problem is
that I'm not really sure what operations I will need
to perform on RI variables. That is, I'm not convinced
that I will really have to switch based on type in all
THAT many places. (...)

You're right. I guess I'm just trying too hard.

But still, I'm curious how do others handle similar design issues.

Cheers.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,021
Latest member
AkilahJaim

Latest Threads

Top