Patrick said:
On Mon, 20 Dec 2004, Tom Widmer wrote:
Hi Tom,
Considering again the trait example
template <class _Tp>
struct type : typeClass
{
static string name();
static _Tp convert(string str);
};
template<>
struct type<bool>
{
// implementation goes here
};
etc..
How would the second type map<int,?> myMap looks to allow the following
myMap[0] = type<bool>();
myMap[1] = type<float>();
map<int, typeClass> myMap;
Are you sure it is not map<int, typeClass*> ?
It should be, or references. With references, you'll need to make sure
the objects exist for the life of the map. Since the safest way is with
heap-allocated objects, you'd be better off with pointers.
I am trying to write a trait for a parser class. In principle I would like
to insert an options with a method which looks like
insert(optKey, type<optType>(), "optName", "optDescription", any(optVar))
OptKey is used as the first element of map<int, typeClass*> myMap.
The second data contains a pointer to the trait class for the type
optTYpe.
So it should be (asssuming optKey is or can be converted to int)
insert(optKey, new optType_type, ...);
optType_type should be class derived from type. You could use a factory
to make it easier. The map should look like
std::map<int, type*>
What yo need is run-time polymorphism and you can't achieve that with
templates, which requires all types to be known at compile-time.
It should contain
* the name of the type (for example real for float, double).
This is to be used for example for a --help option (as name())
That should be easy, since you can use a virtual function in class type
returning a std::string.
* a convert algorithm from string to optType to be used when calling the
menber function parse(optKey, optVar)
That will be impossible, though it would be nice. You will need to
design a framework à la Java with a base class (such as Object) and to
return objects by pointer/reference, wherever they are allocated (heap,
static...). You can use covariant return types to make your life easier.
* there might be need for another trait to test the domain of validity of
the option value (for example only positive integer)
That should be easy also, just use a virtual function.
* "optName" is the option name
* "optDescription" is a short description to be used for a --help option
* any(optVar) stores the current (default) value of the var in a any
container (boost).
Ok.
Do you understand what I want to do? Any more ideas?
C++ is not a language with which it is easy to play with types. The
kind of polymorphism you look for can only be implemented with a
hierarchy of classes.
1) Design a hierarchy of classes (Integer, Bool, Float...) all deriving
from Object.
Object has a pure virtual print(std:

stream&) or something and a
static key() function which returns an invalid key. Derived classes
should override that static function.
Add an global operator<< which takes a const Object&. Call print on
it.
class Object
{
public:
virtual ~Object();
virtual void print(std:

stream &stream) const = 0;
};
class Integer : public Object
{
private:
int value_;
public:
Integer(int value);
virtual void print(std:

stream &stream) const
};
....
2) Design a hierarchy of classes (IntegerConverter, BoolConverter,
FloatConverter...) all deriving from Converter. Converter will have
the following pure virtual functions :
. std::string name() const
returns the converter's name (such as "bool" or "integer").
. Object *convert(std::string s) ( Object* can be covariant )
parses the string and returns the corresponding object. return
0 if parsing failed.
. static Key key()
returns a unique number (Key could be an int). this function
may be a problem to maintain, just make sure what you return is
unique to the class.
class Converter
{
public:
typedef int Key;
public:
virtual std::string name() const = 0;
virtual Object *convert(std::string s) = 0;
static Key key() { return 0; }
};
class BoolConverter : public Converter
{
public:
virtual std::string name() const;
virtual Bool *convert(std::string s); // note : covariant
static Key key() { return unique_number; }
};
....
3) Make an Option class containing a Key and a name (such as "help" or
"an_option").
class Option
{
private:
Converter::Key key_;
std::string name_;
public:
Option(Converter::Key key, std::string name);
Converter::Key key() const;
std::string name() const
};
4) Make a Types class containing a map<Key, Converter*> which will have
all the converters (and will not forget to delete them

. Types
has something like get(Key k) which returns 0 or the correct
converter.
class Types
{
private:
typedef std::map<Converter::Key, Converter*> Map;
Map map_;
public:
Types();
~Types()
Converter *get(Converter::Key key);
void add(Converter::Key key, Converter *c);
};
5) Make a class Format which gets a reference to the Types object and
contains a vector of Options. Add a parse(std::string s) member
function. More on that one later.
class Format
{
private:
typedef std::vector<Option> Options;
Options options_;
Types &types_;
public:
Format(Types &types);
void add(Option o);
void parse(std::string what);
};
6) Have your main() function create a Types object and fill it with
converters. Then, create a Format object, giving it a reference to
the Types object, and fill it with options. Finally, give a string
to Format to parse it.
int main()
{
Types types;
types.add(BoolConverter::key(), new BoolConverter);
types.add(IntegerConverter::key(), new IntegerConverter);
Format format(types);
format.add(Option(BoolConverter::key(), "opt1"));
format.add(Option(IntegerConverter::key(), "opt2"));
format.add(Option(BoolConverter::key(), "opt3"));
format.parse("1 10 0");
}
Have Format:

arse() fill a vector of std::string containing the tokens
of the given string. Iterate over all these tokens and the option list,
get the converter for that option and convert the token. You should get
an Object* or 0 if the conversion failed.
void Format:

arse(std::string what)
{
typedef std::vector<std::string> Tokens;
Tokens tokens;
// tokenize 'what' into 'tokens'
Tokens::iterator token_itor = tokens.begin();
Options::iterator option_itor = options_.begin();
for ( ; token_itor != tokens.end() && option_itor !=
options_.end(); ++token_itor, ++option_itor)
{
Option *o = &*option_itor;
std::string t = *token_itor;
Converter *converter = types_.get(o->key());
if ( converter == 0 )
{
// type not found, that's a problem with the keys
continue;
}
Object *value = converter->convert(t);
if ( value != 0 )
{
std::cout << "got " << *value
<< " of type " << converter->name()
<< std::endl;
}
else
{
std::cout << "couldn't parse '" << t
<< "' to a " << converter->name()
<< std::endl;
}
delete value;
}
}
So with a string "1 10 0", you get
got true of type bool
got 10 of type integer
got false of type bool
and a string "1 a 0" you get
got true of type bool
couldn't parse 'a' to a integer
got false of type bool
I actually made the whole code (and it works quite well), so if you need
help with that design, I'll show you a bit more (but not too much
Jonathan