1

I've made a few new classes for help in reading CSV files in a C++ program, based on code suggested in another stackoverflow post here. Here is the file declaring and defining the classes, along with some helper functions defined outside the classes to convert to different data types:

/**
@file CSVHandler.h
@brief Declaration of a class for handling CSV file reading and writing.
*/

#ifndef SRC_THAMESLIB_CSVHANDLER_H_
#define SRC_THAMESLIB_CSVHANDLER_H_

#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>

/**
@class Declare the CSVRow class

This class was taken from an answer at stackoverflow.com:
https://stackoverflow.com/questions/1120140/how-can-i-read-and-parse-csv-files-in-c
*/
class CSVRow {
public:
  std::string_view operator[](std::size_t index) const {
    return std::string_view(&m_line[m_data[index] + 1],
                            m_data[index + 1] - (m_data[index] + 1));
  }
  std::size_t size() const { return m_data.size() - 1; }
  void readNextRow(std::istream &str) {
    std::getline(str, m_line);

    m_data.clear();
    m_data.emplace_back(-1);
    std::string::size_type pos = 0;
    while ((pos = m_line.find(',', pos)) != std::string::npos) {
      m_data.emplace_back(pos);
      ++pos;
    }
    // This checks for a trailing comma with no data after it.
    pos = m_line.size();
    m_data.emplace_back(pos);
  }

private:
  std::string m_line;
  std::vector<int> m_data;
};

std::istream &operator>>(std::istream &str, CSVRow &data) {
  data.readNextRow(str);
  return str;
}

/**
@class Declare the CSVIterator class

This class was taken from an answer at stackoverflow.com:
https://stackoverflow.com/questions/1120140/how-can-i-read-and-parse-csv-files-in-c
*/
class CSVIterator {
public:
  typedef std::input_iterator_tag iterator_category;
  typedef CSVRow value_type;
  typedef std::size_t difference_type;
  typedef CSVRow *pointer;
  typedef CSVRow &reference;

  CSVIterator(std::istream &str) : m_str(str.good() ? &str : nullptr) {
    ++(*this);
  }
  CSVIterator() : m_str(nullptr) {}

  // Pre Increment
  CSVIterator &operator++() {
    if (m_str) {
      if (!((*m_str) >> m_row)) {
        m_str = nullptr;
      }
    }
    return *this;
  }
  // Post increment
  CSVIterator operator++(int) {
    CSVIterator tmp(*this);
    ++(*this);
    return tmp;
  }
  CSVRow const &operator*() const { return m_row; }
  CSVRow const *operator->() const { return &m_row; }

  bool operator==(CSVIterator const &rhs) {
    return ((this == &rhs) ||
            ((this->m_str == nullptr) && (rhs.m_str == nullptr)));
  }
  bool operator!=(CSVIterator const &rhs) { return !((*this) == rhs); }

private:
  std::istream *m_str;
  CSVRow m_row;
};

/**
@class Declare the CSVRange class

This class was taken from an answer at stackoverflow.com:
https://stackoverflow.com/questions/1120140/how-can-i-read-and-parse-csv-files-in-c
*/
class CSVRange {
  std::istream &stream;

public:
  CSVRange(std::istream &str) : stream(str) {}
  CSVIterator begin() const { return CSVIterator{stream}; }
  CSVIterator end() const { return CSVIterator{}; }
};

// Handler functions to convert to different data types
int row2int(const std::string_view &string) {
  int result = std::stoi(std::string(string));
  return (result);
}

double row2double(const std::string_view &string) {
  double result = std::stod(std::string(string));
  return (result);
}

#endif // SRC_THAMESLIB_CSVHANDLER_H_

The problem is that when I now compile the library I get a linking error saying that these helper functions and the overloaded >> operator are duplicates:

[ 73%] Built target thameslib
[ 78%] Linking CXX executable thames
duplicate symbol 'row2int(std::__1::basic_string_view<char, std::__1::char_traits<char>> const&)' in:
    /Users/jwbullard/Software/THAMES/build/CMakeFiles/thames.dir/src/thames.cc.o
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[11](Lattice.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[9](KineticController.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[4](Controller.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[15](StandardKineticModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[13](PozzolanicModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[12](ParrotKillohModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[10](KineticModel.cc.o)
duplicate symbol 'row2double(std::__1::basic_string_view<char, std::__1::char_traits<char>> const&)' in:
    /Users/jwbullard/Software/THAMES/build/CMakeFiles/thames.dir/src/thames.cc.o
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[11](Lattice.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[9](KineticController.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[4](Controller.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[15](StandardKineticModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[13](PozzolanicModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[12](ParrotKillohModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[10](KineticModel.cc.o)
duplicate symbol 'operator>>(std::__1::basic_istream<char, std::__1::char_traits<char>>&, CSVRow&)' in:
    /Users/jwbullard/Software/THAMES/build/CMakeFiles/thames.dir/src/thames.cc.o
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[11](Lattice.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[9](KineticController.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[4](Controller.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[15](StandardKineticModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[13](PozzolanicModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[12](ParrotKillohModel.cc.o)
    /Users/jwbullard/Software/THAMES/build/src/thameslib/libthameslib.a[10](KineticModel.cc.o)
ld: 3 duplicate symbols
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [thames] Error 1

I thought that having the header guards would prevent this kind of duplication, but I guess not. Can anyone give me some advice on how to correct this?

5
  • 4
    You need the inline keyword in front of those function definitions in the header. Commented Jul 22 at 22:08
  • 1
    Note that every file you compile (a translation unit is processed separately by the compiler. The include guard prevents multiple inclusion in the same translation unit, but cannot prevent multiple translation units from including the same header. Commented Jul 22 at 22:25
  • Near duplicate: How a multiple times #included guarded header file will be inside different translation units? Commented Jul 22 at 22:31
  • 1
    FYI, if you modify a function inside a header file, all code that depends on or includes the header file will need to be rebuilt. This is a major reason why function implementations are commonly not placed in header files, but source files. The source file allows you to change the content of the function without have to build a lot files. Commented Jul 22 at 22:36
  • 1
    I recommend against have function implementations inside a header file unless absolutely necessary. Usually, platforms are fast enough that inlining is not necessary. Commented Jul 22 at 22:38

1 Answer 1

9

I thought that having the header guards would prevent this kind of duplication, but I guess not.

The header guard prevents duplicate symbols only when your header file is included multiple times within a single translation unit (TU), ie a .cc file in your case.

But, when the header file is included in multiple TUs, each TU defines the same symbols within itself. TUs are compiled independently of each other. The linker then ends up finding the duplicates when it brings together the .o object files for those multiple TUs. That is why you get a linker error and not a compiler error.

To fix this, you can either:

  • add inline to the affected functions, eg:

    inline std::istream &operator>>(std::istream &str, CSVRow &data) {
      ...
    }
    
    inline int row2int(const std::string_view &string) {
      ...
    }
    
    inline double row2double(const std::string_view &string) {
      ...
    }
    

or:

  • split the code into separate .h (declarations only) and .cc (definitions only) files, and then link the .cc file:

    CSVHandler.h

    std::istream &operator>>(std::istream &str, CSVRow &data);
    int row2int(const std::string_view &string);
    double row2double(const std::string_view &string);
    

    CSVHandler.cc

    #include "CSVHandler.h"
    
    std::istream &operator>>(std::istream &str, CSVRow &data) {
      ...
    }
    
    int row2int(const std::string_view &string) {
      ...
    }
    
    double row2double(const std::string_view &string) {
      ...
    }
    
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.