views:

1165

answers:

3

I have this following code:

template <class T>
bool loadCSV (const QString &filename, map<T,int> &mapping){
    QFile boxfile;
    boxfile.setFileName(filename);
    QString line;
    QStringList list;

    if (!boxfile.open(QIODevice::ReadOnly)){
     cout << "Could not open box data file." << endl;
     return false;
    }

    QTextStream stream2( &boxfile ); 

    while (!stream2.atEnd()){
     line = stream2.readLine();
     list = line.split(',');
     mapping[list.front().toInt()]=list.back().toInt();  
    }

    return true;
}

It takes a CSV file and sticks it into a:

map<int, int> mapping

structure. Now this is done as a template so that I can also use to stick data into a

map<string, int> mapping

structure. Now to do this, the final line in the while loop needs to be changed and I am not sure what is the best way to achieve this.

I could think of a few ways:

  1. some how detect the type and have some sort of conditional line in there (I'm not actually sure if this is possible and if so how to do this.

  2. add a class function to QStringList to do this.

I tried option 2 and did this:

void QStringList::cInsert(map<int,int> &mapping){
    mapping[this->front().toInt()]=this->back().toInt();
}

void QStringList::cInsert(map<string,int> &mapping){
    mapping[(this->front()).toAscii()]=this->back().toInt();
}

This didn't work as I also needed to define these functions in the QStringList class and so this would get a bit messy. Instead. I try to inherit from QStringList:

class myQStringList: public QStringList{
    public:
     void cInsert(map<int,int> &mapping);
     void cInsert(map<string,int> &mapping);
};

void myQStringList::cInsert(map<int,int> &mapping){
    mapping[this->front().toInt()]=this->back().toInt();
}

void myQStringList::cInsert(map<string,int> &mapping){
    mapping[(this->front()).toAscii()]=this->back().toInt();
}

And then change the code:

template <class T>
bool loadCSV (const QString &filename, map<T,int> &mapping){
    QFile boxfile;
    boxfile.setFileName(filename);
    QString line;
    myQStringList list;   

if (!boxfile.open(QIODevice::ReadOnly)){
 cout << "Could not open box data file." << endl;
 return false;
}

QTextStream stream2( &boxfile ); 

while (!stream2.atEnd()){
 line = stream2.readLine();
 list = line.split(',');
     list.cInsert(mapping);
}

return true;}

BUT. I get an error in relation to the line.split/list assignment:

main.cpp:123: error: no match for 'operator=' in 'list = QString::split(const QChar&, QString::SplitBehavior, Qt::CaseSensitivity) const(((const QChar&)(&QChar(44))),  KeepEmptyParts,  CaseSensitive)'

I'm not sure what this error is and not sure if it is to do with the assign/copy operator not being inherited?

And in relation to the actual new class, I get this error:

main.cpp:104: error: no match for 'operator[]' in 'mapping[QString::toAscii() const()]'
c:/qt/mingw/bin/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/bits/stl_map.h:332: note: candidates are: _Tp& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const _Key&) [with _Key = std::string, _Tp = int, _Compare = std::less<std::string>, _Alloc = std::allocator<std::pair<const std::string, int> >]
main.cpp: In function `bool loadCSV(const QString&, std::map<T, int, std::less<_Key>, std::allocator<std::pair<const T,int> > >&) [with T = int]':

Which I simply don't understand. Can anyone please explain?

Also, I'm not sure if the way I'm going about this is correct and would appreciate advice in relation to this also. Thanks.

A: 

I solved the problem by explicitly casting:

list = (const myQStringList&) line.split(',');
void myQStringList::cInsert(map<string,int> &mapping){
    mapping[(string)(this->front()).toAscii()]=this->back().toInt();
}
You also have QString::toStdString() method. And QString::fromStdString() as well...
Cătălin Pitiș
Don't forget to accept your own answer ! :)
Magnus Skog
+2  A: 

I would move the conversion from QString to type T outside your function. Therefore, have something like:

template< typename T>
struct converter { };

template<>
struct converter< int>
{
static int convert( const QString& source)
{
    return source.toInt();
}
};

template<>
struct converter< std::string>
{
static std::string convert( const QString& source)
{
    return source.toStdString();
}
};

template< typename T>
T convertFromString( const QString& source)
{
    return converter<T>::convert( source);
}

Then, use this convert function in your loop (inside while):

Instead of

mapping[list.front().toInt()]=list.back().toInt();

write:

mapping[ convert<T>(list.front())]=list.back().toInt();

Note: The class converter (and its two specializations) are defined so the convert function can be used as a template function (similar to static_cast or to boost::lexical_cast if you like).

I didn't try to compile the code. It is just an idea.

Cătălin Pitiș
A: 

When it comes to CSV files, Qt provides neat functionality via QString::section function. So, to find out necessary columns I process the header as follows:

int AmountInd = (line.left(line.indexOf("Amount")).count(',');

to get the index of that column. Then, for forthcoming lines I extract that column's substring:

QString AmountStr = (line.section(',', AmountInd, AmountInd);
double Amount = AmountStr.toDouble();

Hope you'll find section function usefull ;-)

MadH