Heterogeneous Container: avoid ifs and casts?

M

Markus Dehmann

I defined a base class in order to put heterogeneous values into a
standard container: All values that I store in the container are
derived from my base class.

Now when I iterate over the elements of the container and want my
original values back - can I avoid excessive casting and if
statements? (see my sample code below.) Can I make it more elegant?

#include <iostream>
#include <vector>
using namespace std;

class Value{
public:
virtual ~Value(){}
};

template< typename T >
class SpecVal: public Value{
private:
T m_t;
public:
SpecVal (T const& t = T()): m_t(t) {}
T &getValue() {return m_t;}
};

void processString(const string &s){
cout << "String: " << s << endl;
}

void processFloat(const float &f){
cout << "Float: " << f << endl;
}

int main(){
vector<Value *> v;
v.push_back(new SpecVal<float>(3.5));
v.push_back(new SpecVal<string>("test"));
for(vector<Value *>::iterator i = v.begin(); i != v.end(); ++i){
if(SpecVal<float>* f = dynamic_cast<SpecVal<float>*>(*i)){
processFloat(f->getValue());
}else if(SpecVal<string>* s = dynamic_cast<SpecVal<string>*>(*i)){
processString(s->getValue());
}
delete *i;
}
}
 
P

Phlip

Markus said:
I defined a base class in order to put heterogeneous values into a
standard container: All values that I store in the container are
derived from my base class.
Now when I iterate over the elements of the container and want my
original values back - can I avoid excessive casting and if
statements? (see my sample code below.) Can I make it more elegant?

Generally, you need to read 's endless threads about
downcasting.

Specifically...
class Value{
public:
virtual ~Value(){}

virtual void stream(ostream & o) const = 0;

ostream & operator<<(ostream &o, Value const & val)
{
val.stream(o);
return o;
}

Now provide a custom implementation of stream() for each derived type.

The point of OO programming in general is to push interfaces up into
abstract class, and implementations down into concrete classes (and out into
delegates). So anything you plan to do generically to all Values deserves a
virtual method in the base class.
 
V

Victor Bazarov

Markus said:
I defined a base class in order to put heterogeneous values into a
standard container: All values that I store in the container are
derived from my base class.

Now when I iterate over the elements of the container and want my
original values back - can I avoid excessive casting and if
statements? (see my sample code below.) Can I make it more elegant?

The only way to make it more elegant is to define polymorphically
accessible interface and use it. I realise that it's not always
possible with straight types like 'int' and 'string', but do you
really expect so different types like your 'SpecVal<>', to somehow
usefully co-exist in the same container?

Depending on the purpose of this exercise, you might consider these
couple of opportunities. First solution, store the "type information"
in the class itself, so that you don't use the 'dynamic_cast<
SpecVal<string>* >()' ugliness, but instead use, say,

switch (i->getType()) {
case TYPE_STRING:
...
case TYPE_INT:

and so on. Of course, you will limit yourself to the number of
pre-defined type values and will have to let the base class know
what the derived classes are up to, which is against some principles
of OOD. Second, you could define 'toString' member in the 'Value'
class and always make your derived classes convert their stored
values to a string. That way you keep the straight interface, but
you incur the penalty of converting the values back and forth.

In any case, without knowing what precisely you're going to do with
the stored values, it's rather impossible to give perfect advice,
only guesses.
#include <iostream>
#include <vector>
using namespace std;

class Value{
public:
virtual ~Value(){}
};

template< typename T >
class SpecVal: public Value{
private:
T m_t;
public:
SpecVal (T const& t = T()): m_t(t) {}
T &getValue() {return m_t;}
};

void processString(const string &s){
cout << "String: " << s << endl;
}

void processFloat(const float &f){
cout << "Float: " << f << endl;
}

int main(){
vector<Value *> v;
v.push_back(new SpecVal<float>(3.5));
v.push_back(new SpecVal<string>("test"));
for(vector<Value *>::iterator i = v.begin(); i != v.end(); ++i){
if(SpecVal<float>* f = dynamic_cast<SpecVal<float>*>(*i)){
processFloat(f->getValue());
}else if(SpecVal<string>* s = dynamic_cast<SpecVal<string>*>(*i)){
processString(s->getValue());
}
delete *i;
}
}


Victor
 
M

Markus Dehmann

Generally, you need to read 's endless threads about
downcasting.

Specifically...


virtual void stream(ostream & o) const = 0;


ostream & operator<<(ostream &o, Value const & val)
{
val.stream(o);
return o;
}

Now provide a custom implementation of stream() for each derived type.

Thanks. Actually, I don't want to do anything with streams. I really want
the original float or string etc value back. Insofar, my code example was
misleading:

void processFloat(const float &f){
cout << "Float: " << f << endl;
}

It should be sth like:
float processFloat(const float &f){
return f * 10.0; // do sth with float
}
 
B

bartek

Thanks. Actually, I don't want to do anything with streams. I really
want the original float or string etc value back. Insofar, my code
example was misleading:

void processFloat(const float &f){
cout << "Float: " << f << endl;
}

It should be sth like:
float processFloat(const float &f){
return f * 10.0; // do sth with float
}

Did you consider using the visitor pattern?

E.g. (derived from original example)

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>

using std::vector;
using std::string;
using std::eek:stream;
using std::cout;
using std::for_each;

// need a forward decl.
template <class T> class SpecVal;

// visitor ABC
class Visitor {
public:
virtual ~Visitor() { }
virtual void operator()(SpecVal<float>&) const = 0;
virtual void operator()(SpecVal<string>&) const = 0;
};

// value ABC
class Value {
public:
virtual ~Value() { }
virtual void apply(Visitor const&) = 0;
};

// concrete value
template <class T>
class SpecVal : public Value {
T m_t;
public:
SpecVal(T const& t = T()) : m_t(t) { }
T& get() { return m_t; }
virtual void apply(Visitor const& v) { v(*this); }
};

// visitor implementation
class PrintVisitor : public Visitor {
ostream& out;
public:
PrintVisitor(ostream& out) : out(out) { }
virtual void operator()(SpecVal<float>& val) const
{ cout << "float:" << val.get() << '\n'; }
virtual void operator()(SpecVal<string>& val) const
{ cout << "string:" << val.get() << '\n'; }
};


int main() {
vector<Value*> v;

v.push_back(new SpecVal<float>(3.5f));
v.push_back(new SpecVal<string>("test"));

PrintVisitor visitor(cout);

vector<Value*>::iterator it(v.begin());
for (; it != v.end(); ++it)
(*it)->apply(visitor);

return 0;
}
 
D

Daniel T.

Phlip said:
Generally, you need to read 's endless threads about
downcasting.

Specifically...


virtual void stream(ostream & o) const = 0;


ostream & operator<<(ostream &o, Value const & val)
{
val.stream(o);
return o;
}

Now provide a custom implementation of stream() for each derived type.

The point of OO programming in general is to push interfaces up into
abstract class, and implementations down into concrete classes (and out into
delegates). So anything you plan to do generically to all Values deserves a
virtual method in the base class.

I agree with Philip, however there is another alternitive in this
case... 'boost::any' <www.boost.org> is probably worth looking into.
 
T

tom_usenet

Thanks. Actually, I don't want to do anything with streams. I really want
the original float or string etc value back.

But you want the value back in order to do something with it. Ideally,
put the operations in the base class interface. You can also use the
likes of the visitor pattern if the set of operations you want to
perform on Value types is unknown, but the set of Value subclasses is
known an finite.

If you're putting these values in a container together, there must be
something that you want to do to all of them. Value as written has no
behaviour - it is little better than using void* really. Give it the
behaviour you need from Values. What operations will you perform on
them?

Tom
 
A

Amadeus W.M.

I defined a base class in order to put heterogeneous values into a
standard container: All values that I store in the container are
derived from my base class.

Now when I iterate over the elements of the container and want my
original values back - can I avoid excessive casting and if
statements? (see my sample code below.) Can I make it more elegant?

#include <iostream>
#include <vector>
using namespace std;

class Value{
public:
virtual ~Value(){}
[quoted text muted]

template< typename T >
class SpecVal: public Value{
private:
T m_t;
public:
SpecVal (T const& t = T()): m_t(t) {}
T &getValue() {return m_t;}
[quoted text muted]

void processString(const string &s){
cout << "String: " << s << endl;
[quoted text muted]

void processFloat(const float &f){
cout << "Float: " << f << endl;
[quoted text muted]

int main(){
vector<Value *> v;
v.push_back(new SpecVal<float>(3.5));
v.push_back(new SpecVal<string>("test"));
for(vector<Value *>::iterator i = v.begin(); i != v.end(); ++i){
if(SpecVal<float>* f = dynamic_cast<SpecVal<float>*>(*i)){
processFloat(f->getValue());
}else if(SpecVal<string>* s = dynamic_cast<SpecVal<string>*>(*i)){
processString(s->getValue());
}
delete *i;
}
[quoted text muted]


How about this: implement an abstract class Value, with a
virtual void process() function. Then derive any type you need
from Value, and override the process() function. In more detail:

class Value
{
// private/protected members
public:
virtual void process() = 0;
// more public members.
};

class StringValue : public Value
{

public:
void process() { // Your processString() function here. };
};

class FloatValue : public Value
{

public:
void process() { // Your processFloat() function here. };
};

Then, in main()

vector<Value*> v;

// push back some Value* in v as needed;
// Initialize each one to the desired type:

v[0]=new StringValue("test");
v[1]=new FloatValue(3.5);

Then

v[0]->process(); // process string
v[1]->process(); // process float

That's the way I'd do it.
 
B

bartek

Typos, etc. corrected below.

Did you consider using the visitor pattern?

E.g. (derived from original example)

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>

using std::vector;
using std::string;
using std::eek:stream;
using std::cout;
using std::for_each;

std::for_each as well...
// need a forward decl.
template <class T> class SpecVal;

// visitor ABC
class Visitor {
public:
virtual ~Visitor() { }
virtual void operator()(SpecVal<float>&) const = 0;
virtual void operator()(SpecVal<string>&) const = 0;
};

// value ABC
class Value {
public:
virtual ~Value() { }
virtual void apply(Visitor const&) = 0;
};

// concrete value
template <class T>
class SpecVal : public Value {
T m_t;
public:
SpecVal(T const& t = T()) : m_t(t) { }
T& get() { return m_t; }
virtual void apply(Visitor const& v) { v(*this); }
};

// visitor implementation
class PrintVisitor : public Visitor {
ostream& out;
public:
PrintVisitor(ostream& out) : out(out) { }
virtual void operator()(SpecVal<float>& val) const
{ cout << "float:" << val.get() << '\n'; }

Of course, I made a typo in the above line ... should be:

{ out << "float:" << val.get() << '\n'; }

virtual void operator()(SpecVal<string>& val) const
{ cout << "string:" << val.get() << '\n'; }

Ditto...
{ out << "string:" << val.get() << '\n'; }
 

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,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top