Federico Fuga

Engineering, Tech, Informatics & science

A compact CSV parser using C++ TMP

07 Mar 2016 17:24 +0000 c++ Metaprogramming
How many times have you implemented a CSV parser to fill a list of stucts from a text file?







Personally, it happens to me about every time I have new project. Though it is a simple task that requires no more than half an hour to build and debug, it is boring, error prone and repetitive. The algorithm is quite trivial, read each line, split it in a vector of string using some separator (about always a comma, hence the "Comma Separated" name), use something to convert each field in a PoD (int, double, whatever) and fill a struct. 







Here's a general solution that makes use of Metaprogramming to implement a parser with strong type checking.







I'll leave you as an exercise to implement the stream process, here we just take a string and parse it to a variadic tuple using boost::lexical_cast and some TMP.







#include <bits/c++config.h>
#include <type_traits>
#include <string>
#include <tuple>

#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

template <std::size_t I = 0, typename Iterator, typename ...Ts>
inline typename std::enable_if< I == sizeof...(Ts), void>::type
parse( std::tuple<Ts...> &tuple, Iterator it)
{
};

template <std::size_t I = 0, typename Iterator, typename ...Ts>
inline typename std::enable_if< I < sizeof...(Ts), void>::type
parse( std::tuple<Ts...> &tuple, Iterator it)
{
    std::get<I>(tuple) = boost::lexical_cast<typename std::tuple_element<I, std::tuple<Ts...> >::type >(*it);
    parse<I+1, Iterator, Ts...>(tuple, ++it);
};

int main(int argc, char *argv[])
{
    std::string sample = "first,second,3,4,5.01,sei";
    std::tuple<std::string,std::string,int,int,double,std::string> values;
    std::vector<std::string> fields;

    boost::split(fields, sample, boost::is_any_of(","));

    std::cout << fields << std::endl;

    parse(values, fields.begin());

    std::cout << values << std::endl;
}






 







My favourite use of this helper is within a reader loop that calls a functor passed as an argument that takes the converted tuple and make the final use.







Something like this....







 







    using VesselDataType = std::tuple<std::string,
            double,double,double,double,double,double,double,
            double,double,double,double,double,double,double>;

    int i = 0;
    auto VesselLoaderFunc = [&i] (VesselDataType data) {
        std::cout << i << " Vessel: " << data << std::endl;
        ++i;
    };
    if (!reader.importFromStream<VesselDataType>(strm,"|", VesselLoaderFunc)) {
        std::cout << "Error loading vessels" << std::endl;
    }







 







Happy coding :-)