trait technique?

Discussion in 'C++' started by Patrick Guio, Dec 18, 2004.

  1. Patrick Guio

    Patrick Guio Guest

    Dear all,

    I am not sure this is really a trait technique but I have some trouble
    with the following. I would like to create a small object that carry
    information about a value type (the template) and a convert member
    function from string to the value type.

    To do this I have defined

    template <class _Tp>
    struct type
    { };

    and defined the specialisation I want like

    template<>
    struct type<char>
    {
    static string name() { return string("character"); }
    static char convert(const string &value)
    {
    return *value.c_str();
    }
    };

    template<>
    struct type<int>
    {
    static string name() { return string("integer"); }
    static int convert(const string &value)
    {
    return static_cast<int>(strtol(value.c_str(), 0, 10));
    }
    };

    and so on....

    Now I can define a test function

    template<class T>
    void test(const string & s)
    {
    cout << "s=" << s << "\ttype=" << type<T>::name() << " converted " <<
    type<T>::convert(s) << endl;
    }

    and calls like

    test<char>("c");
    test<int>("2004");

    works nicely.

    Now I would like to extend to class type containers like vector

    I tried

    template <class T>
    struct type< vector<T> >
    {
    static string name() { return string("vector<" + type<T>::name() + ">");
    }
    static vector<T> convert(const string &value)
    {
    // To implement for comma-separated T values
    return vector<T>(0);
    }
    };


    test< vector<int> >("1,2,3,4");

    But my compiler is complaining. Any idea how to do that?


    Sincerely,
    Patrick
    Patrick Guio, Dec 18, 2004
    #1
    1. Advertising

  2. Patrick Guio wrote:
    > I am not sure this is really a trait technique but I have some trouble
    > with the following. I would like to create a small object that carry
    > information about a value type (the template) and a convert member
    > function from string to the value type.
    >
    > To do this I have defined
    >
    > template <class _Tp>
    > struct type
    > { };
    >
    > and defined the specialisation I want like
    >
    > template<>
    > struct type<char>
    > {
    > static string name() { return string("character"); }
    > static char convert(const string &value)
    > {
    > return *value.c_str();
    > }
    > };
    >
    > template<>
    > struct type<int>
    > {
    > static string name() { return string("integer"); }
    > static int convert(const string &value)
    > {
    > return static_cast<int>(strtol(value.c_str(), 0, 10));
    > }
    > };
    >
    > and so on....
    >
    > Now I can define a test function
    >
    > template<class T>
    > void test(const string & s)
    > {
    > cout << "s=" << s << "\ttype=" << type<T>::name() << " converted " <<
    > type<T>::convert(s) << endl;
    > }
    >
    > and calls like
    >
    > test<char>("c");
    > test<int>("2004");
    >
    > works nicely.
    >
    > Now I would like to extend to class type containers like vector
    >
    > I tried
    >
    > template <class T>
    > struct type< vector<T> >
    > {
    > static string name() { return string("vector<" + type<T>::name() +
    > ">"); }
    > static vector<T> convert(const string &value)
    > {
    > // To implement for comma-separated T values
    > return vector<T>(0);
    > }
    > };
    >
    >
    > test< vector<int> >("1,2,3,4");
    >
    > But my compiler is complaining. Any idea how to do that?


    What is your compiler complaining about?

    #include <vector>

    template<class T> struct A { int a; };
    template<class T> struct A<std::vector<T> > { char b; };

    template<class T> A<T> bar(T t);

    int main() {
    std::vector<int> v;
    bar(v).a; // error: A<vector<int> > has no member 'a'
    bar(v).b; // OK
    }

    Victor
    Victor Bazarov, Dec 18, 2004
    #2
    1. Advertising

  3. Patrick Guio

    Patrick Guio Guest

    On Sat, 18 Dec 2004, Victor Bazarov wrote:


    > What is your compiler complaining about?
    >
    > #include <vector>
    >
    > template<class T> struct A { int a; };
    > template<class T> struct A<std::vector<T> > { char b; };
    >
    > template<class T> A<T> bar(T t);
    >
    > int main() {
    > std::vector<int> v;
    > bar(v).a; // error: A<vector<int> > has no member 'a'
    > bar(v).b; // OK
    > }
    >
    > Victor
    >


    Oops, my fault, it actually works...
    But I have another question: is there a way to have a container such as
    map or vector that would contain pointers of different specialisations of
    the template (a kind of polymorphic pointer for template class)?

    Considering again the trait example

    template <class _Tp>
    struct type : typeClass
    {
    static string name();
    static _Tp convert(string str);
    };

    template<>
    struct type<bool>
    {
    // implementation
    ....
    };

    template<>
    struct type<float>
    {
    // implementation
    ....
    };

    etc..

    How would the second type map<int,?> myMap looks to allow the following

    myMap[0] = type<bool>();
    myMap[1] = type<float>();


    Sincerely,

    Patrick
    Patrick Guio, Dec 18, 2004
    #3
  4. Patrick Guio wrote:
    > Oops, my fault, it actually works... But I have another question: is
    > there a way to have a container such as map or vector that would contain
    > pointers of different specialisations of the template (a kind of
    > polymorphic pointer for template class)?


    A container cannot contain different types. A vector of int will always
    store ints. Since a class template is not a type (it is a family of
    types), you cannot use it as an element type for a container.

    template <class T> struct S;

    std::vector< S > v; // illegal
    std::vector< S* > v; // illegal

    std::vector< S<int> > v; // ok
    std::vector< S<int>* > v; // ok

    > Considering again the trait example
    >
    > template <class _Tp>
    > struct type : typeClass
    > {
    > static string name();
    > static _Tp convert(string str);
    > };
    >
    > template<>
    > struct type<bool>
    > {
    > // implementation
    > ....
    > };
    >
    > template<>
    > struct type<float>
    > {
    > // implementation
    > ....
    > };
    >
    > etc..
    >
    > How would the second type map<int,?> myMap looks to allow the following
    >
    > myMap[0] = type<bool>();
    > myMap[1] = type<float>();


    Unfortunately, I cannot think of a solution. By using a common base
    class for all these, you won't be able to have your convert() function
    since its return type is a template argument (except by returning
    Java-like pointers to an Object class, part of your framework, of which
    would derive Integer and Bool classes). The thing is, C++ is very rigid
    on types and fiddling with them can be difficult sometimes.

    You could have

    class Converter
    {
    public:
    virtual std::string name() = 0;
    };

    class IntConverter : public Converter
    {
    public:
    virtual std::string name() { ... }
    };

    class BoolConverter : public Converter
    {
    public:
    virtual std::string name() { ... }
    };

    int main()
    {
    std::vector< Converter* > v;

    v.push_back(new IntConverter);
    v.push_back(new BoolConverter);

    // don't forget to delete them
    }

    Perhaps libraries such as boost have something for you, but I don't have
    other ideas.


    Jonathan
    Jonathan Mcdougall, Dec 19, 2004
    #4
  5. Patrick Guio

    Nate Barney Guest

    Jonathan Mcdougall wrote:
    > Patrick Guio wrote:
    >
    >> Oops, my fault, it actually works... But I have another question: is
    >> there a way to have a container such as map or vector that would
    >> contain pointers of different specialisations of the template (a kind
    >> of polymorphic pointer for template class)?

    >
    >
    > A container cannot contain different types. A vector of int will always
    > store ints. Since a class template is not a type (it is a family of
    > types), you cannot use it as an element type for a container.
    >
    > template <class T> struct S;
    >
    > std::vector< S > v; // illegal
    > std::vector< S* > v; // illegal
    >
    > std::vector< S<int> > v; // ok
    > std::vector< S<int>* > v; // ok


    This may or not be useful to the OP, but you can do this:

    template <class T> struct S;

    template <class T2>
    struct S2
    {
    std::vector<S<T2> > v;
    std::vector<S<T2>*> v2;
    };
    Nate Barney, Dec 19, 2004
    #5
  6. Patrick Guio

    Tom Widmer Guest

    Patrick Guio wrote:
    > But I have another question: is
    > there a way to have a container such as map or vector that would contain
    > pointers of different specialisations of the template (a kind of
    > polymorphic pointer for template class)?


    No - you're mixing compile time with runtime. How would you use the
    type<T> object without knowing what T is?

    > Considering again the trait example
    >
    > template <class _Tp>
    > struct type : typeClass
    > {
    > static string name();
    > static _Tp convert(string str);
    > };
    >
    > template<>
    > struct type<bool>
    > {
    > // implementation
    > ...
    > };
    >
    > template<>
    > struct type<float>
    > {
    > // implementation
    > ...
    > };
    >
    > 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;
    myMap[0] = new type<bool>();
    myMap[1] = new type<float>();

    This assumes that typeClass looks like this:

    class typeClass
    {
    public:
    virtual ~typeClass(){}
    virtual string name() const = 0;
    //other type independent functions.
    };

    But what exactly are you doing?

    Tom
    Tom Widmer, Dec 20, 2004
    #6
  7. Patrick Guio

    Patrick Guio Guest

    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*> ?

    > myMap[0] = new type<bool>();
    > myMap[1] = new type<float>();
    >
    > This assumes that typeClass looks like this:
    >
    > class typeClass
    > {
    > public:
    > virtual ~typeClass(){}
    > virtual string name() const = 0;
    > //other type independent functions.
    > };


    I cannot compile as you say.

    > But what exactly are you doing?


    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. 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())
    * a convert algorithm from string to optType to be used when calling the
    menber function parse(optKey, optVar)
    * there might be need for another trait to test the domain of validity of
    the option value (for example only positive integer)

    * "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).

    Do you understand what I want to do? Any more ideas?

    Sincerely,

    Patrick
    Patrick Guio, Dec 21, 2004
    #7
  8. Patrick Guio wrote:
    > 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.

    >>But what exactly are you doing?

    >
    > 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::eek: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::eek:stream &stream) const = 0;
    };

    class Integer : public Object
    {
    private:
    int value_;

    public:
    Integer(int value);
    virtual void print(std::eek: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::parse() 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::parse(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
    Jonathan Mcdougall, Dec 21, 2004
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Weng Tianxiang
    Replies:
    4
    Views:
    3,033
    Weng Tianxiang
    Apr 7, 2005
  2. abir
    Replies:
    3
    Views:
    328
    Triple-DES
    Feb 27, 2008
  3. abir
    Replies:
    2
    Views:
    308
  4. Thufir
    Replies:
    2
    Views:
    91
    Thufir
    Nov 13, 2007
  5. Nephi Immortal
    Replies:
    2
    Views:
    174
    Nephi Immortal
    Feb 15, 2013
Loading...

Share This Page