How to convert a string to a templated type in c++

11,132

Solution 1

I think your basic approach is wrong.
You seem to be trying to use template-meta programming to achieve your goals.
This is probably not a good idea.

A simpler approach is just to use C++ streams.
These stream objects already know how to read all the basic types. And anybody that want to do anything in C++ will add the appropriate input and output operators to stream their class; so it is pretty universal that you will be able to read any type as both key and value (with the restriction that it must fit on one line).

So now you just need to use standard template logic to define an operator that will read two objects of different types on a single line.

Try this:

#include <string>
#include <memory>
#include <fstream>
#include <sstream>
#include <vector>
#include <iterator>
#include <algorithm>

// These can be any types.    
typedef std::string   Key;
typedef int           Value;

// The data type to hold the data.
template<typename K,typename V>
class Data: public std::pair<K, V>
{

};

Here is the code that will read one record from one line of the file:
Note that the data type Data and this input operator are both templated and can thus ready Key/Value pairs of any objects (as long as those objects know how to stream themselves).

template<typename K,typename V>
std::istream& operator>>(std::istream& stream, Data<K,V>& data)
{
    // Read a line into a local string.
    std::string  line;
    std::getline(stream,line);

    // convert the line into a stream and read the key/value from the line
    std::stringstream  linestream(line);
    linestream >> data.first >> data.second;


    // If the linestream is bad, then reading the key/value failed
    // If reading one more `char` from the linestream works then there is extra crap in the line
    // thus we have bad data on a line.
    //
    // In either case set the bad bit for the input stream.
    char c;
    if ((!linestream) || (linestream >> c))
    {
        stream.setstate(std::ios::badbit);
    }
    // return the stream.
    return stream;
}

Now using it simply means using a stream:

int main()
{
    // The input file
    std::ifstream      file("Plop");

    // We will convert the file and store it into this vector.
    std::vector<Data<Key,Value> >  data;

    // Now just copy the data from the stream into the vector.
    std::copy(std::istream_iterator<Data<Key,Value> >(file),
            std::istream_iterator<Data<Key, Value> >(),
            std::back_inserter(data)
            );
}

Note: In the above example a key must be a single word (as it is read using a string). If you want a key as a string that contains a space you need to do some extra work. But that is the subject of another question.

Solution 2

dynamic_cast is to be used in inheritance hierarchies; you're misusing it here.

For such a simple task, you could use streams, I think (warning, untested):

template<class Key, class T>
void EventReportReader<Key, T>::validateFileFormat()
{
    std::string line;
    while( getline( inStream_, line ) ) {
        std::istringstream stream(line);
        Key k; T t;
        stream >> k >> t;

        if (!stream || !stream.eof()) { /*error*/ }
    }
}

Is there a reason you are not actually keeping the values you read ?

EDIT: Let's specialize for strings

template<class Key>
void EventReportReader<Key, std::string>::validateFileFormat()
{
    std::string line;
    while( getline( inStream_, line ) ) {
        size_t const ws = line.find(' ');
        if (ws == std::string::npos) { /* error */ }

        std::istringstream stream(line.substr(0, ws));
        Key k;
        stream >> k;

        if (!stream) { /*error*/ }

        std::string t = line.substr(ws);
        boost::trim(t);
    }
}

Solution 3

Without information you can't be sure whether you got the right type or not. Your value could be a string or an int. For example you could parse it with this algorithm:

  1. Parse it as int, if it's not successful
  2. Parse it as float, if not
  3. It's a string

Again, you could have a successfully parsed int, but you expect a string.


Concerning your implementation: You probably want partial template specialication.

template<class Key> inline
void EventReportReader<Key, int>::validateFileFormat()
{
  // readLine
  // parse as int..
}

template<class Key> inline
void EventReportReader<Key, float>::validateFileFormat()
{
  // readLine
  // parse as float..
}

But it's probably better not to template your function? If you have a "normal" function you can have a parse logic I described before..

Share:
11,132
Barth
Author by

Barth

I work at CERN in the ALICE experiment. I am in charge of the Data Quality Monitoring software. It is developed in C++ on top of ROOT (root.cern.ch). It uses a publisher-subscriber paradigm to achieve scalability and stability of the dozen of agents exchanging hundreds of objects every second.

Updated on June 21, 2022

Comments

  • Barth
    Barth almost 2 years

    I want to read and parse a text file in C++ in a generic way. The file is always made of key-value pairs, one per line. The key is templated as well as the value. I foresee the key and the values to always be a basic type (int, float, string).

    My problem is that I don't know how to create a transform the key or value string into the correct type.

    I tried the following :

    template<class Key, class T> inline
    void EventReportReader<Key, T>::validateFileFormat()
    {
        // Read file line by line and check that the first token is of type Key and the second one of type T
    
        std::string line;
        try {
            boost::regex re( "(\\S+)\\s+(.*)" );
            while( getline( inStream_, line ) ) {
                boost::cmatch matches;
                if( boost::regex_match( line.c_str(), matches, re ) ) {
                    std::cout << re << " matches " << line << std::endl;
                    std::cout << "   1st : " << matches[1] << "\n   2nd : " << matches[2] << std::endl;
                    // test types
                    Key *k = dynamic_cast<Key*>(&matches[1]);
                    T t = dynamic_cast<T>(matches[2]);
                }
            }
        }
        catch( boost::regex_error& e ) {
            // todo problem with regular expression, abort
        }
    }
    

    And the use of this method is as follow :

    // This in turn calls the method validateFileFormat
    EventReportReader<float, int> reader( testFileName );
    

    The result is

    /home/vonhalle/dev/EventBasedReport/libs/event_based_report/EventReportReader.h:121:60: error: cannot dynamic_cast ‘(const boost::sub_match*)matches.boost::match_results::operator[] with BidiIterator = const char*, Allocator = std::allocator >, boost::match_results::const_reference = const boost::sub_match&’ (of type ‘const struct boost::sub_match’) to type ‘float’ (target is not pointer or reference to class) /home/vonhalle/dev/EventBasedReport/libs/event_based_report/EventReportReader.h:122:53: error: cannot dynamic_cast ‘matches.boost::match_results::operator[] with BidiIterator = const char*, Allocator = std::allocator >, boost::match_results::const_reference = const boost::sub_match&’ (of type ‘const struct boost::sub_match’) to type ‘int’ (target is not pointer or reference)

    How should I do it ? Is it even possible ?

    EDIT: The file might look like this if the template is < float, int >

    1.14 5
    2.34 78
    0.56 24
    

    or this if the template is < int, string >

    23 asdf
    45 2222
    1 bbbb
    

    EDIT2:

    The problem statement above is partially wrong. The key is never a string, the value can be a string. Therefore, whatever is before the first space is the key and the rest is the value. Sorry about this mistake.