HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

Parsing a CSV file with a very specific format

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
formatfilewithcsvparsingveryspecific

Problem

I'm not very good with templates, so any general tips would be appreciated.

Basically this class parses a CSV file with a very specific format.

My original idea for this was that I wanted this to be useful in MFC desktop applications and non-MFC applications such as Windows services. Basically, I wanted this class to be compatible with std::string, CString, and possibly _bstr_t for strings and FILE, std::fstream, maybe HANDLE, and CStdioFile for files. I have implemented the std library specializations and now, I'm not sure if implementing all the others would be a good idea.

Another goal by using templates was to reduce the number of header files in my header file. I'm not sure this is a good reason to use templates.

```
// TrainInfo.h

#pragma once

#ifndef TRAININFO_H
#define TRAININFO_H

#include

template
struct TrainInfo {
enum {
// Values omitted
NUM_ENUM
};

std::vector m_info ;

int GetNumberOfWheels () const ;

// Minimum number of elements that should be parsed.
static int const NELEM_MIN_PARSED = TrainInfo::NUM_ENUM - 1 ;
};

template
struct WheelInfo {
enum {
// Values omitted
NUM_ENUM
};

std::vector m_info ;

// Minimum number of elements that should be parsed.
static int const NELEM_MIN_PARSED = WheelInfo::FileIndex + 1 ;
};

struct ProfilePoint {
ProfilePoint (int x = 0, int y = 0) : x (x), y (y) {}
~ProfilePoint () {}
double x ;
double y ;
};

template
struct WheelProfile {
StringType m_strAxleSequenceNumber ;
StringType m_strTrainSide ;
StringType m_strNumPoints ;
std::vector m_vecPoints ;

int GetNumberOfPoints () const ;

static const int NELEM_WHEEL_PROFILE_HEADER = 3 ;
};

template
class CSVInfo {
public:
TrainInfo m_trainInfo ;
std::vector > m_wheels ;
std::vector > m_profiles ;

CSVInfo () ;
~CSVInfo() ;
void Load (const StringType &strFile) ;

private:
TrainInfo

Solution

I've long since wanted to explore doing this sort of thing myself. In different code bases, different string classes are so much more prevalent, and thus more appropriate to keep using. Your approach isn't quite the right way to go, as it scales on the wrong axis. I can totally see removing the templates and using just a single tstring (or even wstring), and realistically I would probably go that way. There are just so many downsides to using templates for large swaths of functionality (like how much typically needs to be moved to the headers). But if you want to keep the string type flexibility, a better approach would be to use traits classes.

Traits classes help reduce coupling. With a traits class approach, you don't need to specialize your class methods for what changes. Instead you specialize what needs to change for how you use it. Create and specialize traits helper methods for each operation you need on a string. The payoff comes in your next method or next class that only uses functionality you've already put on the traits class. The only hard part is figuring out what to make your traits calls look like.

Here's a simplified beginning to traits class following std::string and std::stringstream's semantics. I hope it's easy to follow how this changes which way your code has to scale. I'm far from certain it's doing things "correctly", as MSVC's traits_type implementations on basic_string and friends look very different. (I'm still wrapping my head around how to use traits classes.)

template 
struct StreamTraits
{
    typedef std::basic_stringstream value_type;
};

// Later to handle non basic_stringstream cases, use specialization
// template <>
// struct StreamTraits
// {
//      typedef CStringStream value_type;
// };

template 
struct StringTraits
{
    typedef StringType value_type;
    typedef typename StreamTraits::value_type stream_type;
};

// Use traits to insulate callers from need to specialize
template 
struct TrainInfo
{
    typedef StringTraits StringTraits;
    typedef typename StringTraits::value_type String;
    typedef StreamTraits StreamTraits;
    typedef typename StreamTraits::value_type Stream;

    int GetNumberOfWheels() const;
};

template 
int TrainInfo::GetNumberOfWheels() const
{
    int nWheels = 0;
    const String& strAxleCount = "2";
    Stream s(strAxleCount);
    // the following should probably be encapsulated in a StreamTraits helper method
    s.exceptions(Stream::failbit | Stream::badbit);

    s >> nWheels;
    return nWheels * 2;
}


Note that none of this solves the points your raise. At best it helps focus the headers, as you could have a header with all your string-specific specializations. This header could then be used by your train classes, or by any other class with similar needs. Users of your train classes that need a string type you didn't plan for should be able to provide traits class specializations and then use their own string class.

But it's still a lot of programming and maintenance overhead, so I would avoid it unless you know you need it. I certainly wouldn't do it just to cut down on the number of headers I was including.

Code Snippets

template <typename StringType>
struct StreamTraits
{
    typedef std::basic_stringstream<
        typename StringType::value_type,
        typename StringType::traits_type
    > value_type;
};

// Later to handle non basic_stringstream cases, use specialization
// template <>
// struct StreamTraits<CString>
// {
//      typedef CStringStream value_type;
// };

template <typename StringType>
struct StringTraits
{
    typedef StringType value_type;
    typedef typename StreamTraits<StringType>::value_type stream_type;
};

// Use traits to insulate callers from need to specialize
template <typename StringType>
struct TrainInfo
{
    typedef StringTraits<StringType> StringTraits;
    typedef typename StringTraits::value_type String;
    typedef StreamTraits<StringType> StreamTraits;
    typedef typename StreamTraits::value_type Stream;

    int GetNumberOfWheels() const;
};

template <typename StringType>
int TrainInfo<StringType>::GetNumberOfWheels() const
{
    int nWheels = 0;
    const String& strAxleCount = "2";
    Stream s(strAxleCount);
    // the following should probably be encapsulated in a StreamTraits helper method
    s.exceptions(Stream::failbit | Stream::badbit);

    s >> nWheels;
    return nWheels * 2;
}

Context

StackExchange Code Review Q#35931, answer score: 4

Revisions (0)

No revisions yet.