Edit: I just noticed that within my CSV file, the iterator does work only when there is a newline as the last line in the file. Does this have to do with istream
and not my implementation of this iterator?
I have the following class and iterator: For some reason, when using a for loop to iterate through, the loop exits immediately and doesn't execute any code within the loop. However, when I call get_row()
manually, it works as intended.
template<typename ...ColType>
class CSV
{
public:
using value_type = std::tuple<ColType...>;
class iterator;
explicit CSV(std::istream &in, const char delimiter = ',') : in_(in), delimiter(delimiter)
{
}
std::tuple<ColType...> get_row()
{
auto next = std::stringstream(next_valid_line());
std::tuple<ColType...> tuple;
parse_row<>(next, tuple);
return tuple;
}
[[nodiscard]] inline bool good() const { return in_.good(); }
inline iterator begin() { return iterator(*this); }
inline iterator end() { return iterator(); }
private:
std::istream &in_;
const char delimiter;
[[nodiscard]] std::string next_valid_line() const
{
std::string line;
while (std::getline(in_, line))
{
if (!line.empty() && !isspace(line.front()) && line.rfind(COMMENT_DELIMITER, 0) != 0)
{
return line;
}
}
return "";
}
template<std::size_t Index = 0>
void parse_row(std::stringstream &s, std::tuple<ColType...> &tuple)
{
if constexpr (sizeof...(ColType) > Index)
{
std::string col;
std::getline(s, col, delimiter);
trim(col);
std::stringstream tmp(col);
tmp >> std::get<Index>(tuple);
parse_row<Index + 1>(s, tuple);
}
}
static void trim(std::string &s)
{
const auto is_space = [](char c)
{ return !std::isspace(c); };
s.erase(s.begin(), std::find_if(s.begin(), s.end(), is_space));
s.erase(std::find_if(s.rbegin(), s.rend(), is_space).base(), s.end());
}
};
template<typename... ColType>
class CSV<ColType...>::iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = typename CSV::value_type;
using difference_type = std::size_t;
using pointer = typename CSV::value_type *;
using reference = typename CSV::value_type &;
iterator() : csv_(nullptr)
{
}
explicit iterator(CSV &other) : csv_(other.good() ? &other : nullptr)
{
++(*this);
}
inline iterator &operator++()
{
if (csv_ != nullptr)
{
row_ = csv_->get_row();
if (!csv_->good()) { csv_ = nullptr; }
}
return *this;
}
inline iterator operator++(int i)
{
iterator tmp = *this;
++(*this);
return tmp;
}
inline value_type const &operator*() const { return row_; }
inline value_type const *operator->() const { return &row_; }
inline bool operator==(iterator const &other)
{
return (this == &other) || (csv_ == nullptr && other.csv_ == nullptr);
}
inline bool operator!=(iterator const &other)
{
return !(*this == other);
}
private:
typename CSV::value_type row_;
CSV *csv_;
};
The iterator class is essentially a copy of https://github.com/LizardM4/ballin-octo-tribble/blob/master/csv/csv.h , so I'm confused as to why mine doesn't work. I'm somewhat experienced with C++, however not with iterators.
From what I can tell, csv_
is being erroneously set to nullptr
, possibly because of in_.good()
returning false for some reason? I can't think of anything else. Thanks.