Background
So earlier today I was implementing a thin wrapper to std::ofstream
that allowed me to write to .csv files easily. I wanted to override the <<
operator to write value followed by a comma, and then when the time came for a new line, I would print a backspace character and then a new line. I decided to implement the new line behaviour as a template specialization as follows:
// *this << value; => prints the value to the csv file
// *this << CsvWriter::BREAK; => goes to the next line of the csv file
// Example:
// CsvWriter csv("test.csv", {"Col 1", "Col 2", "Col 3"});
// csv << "Value 1" << 2 << 3.25 << CsvWriter::BREAK;
class CsvWriter {
public:
CsvWriter(const char *fname, std::vector<const char *> headers);
CsvWriter() = delete;
CsvWriter(const CsvWriter &) = delete;
CsvWriter &operator=(const CsvWriter &) = delete;
~CsvWriter() = default;
// Used for the template specialization below
static struct LineBreak {} BREAK;
template <typename T>
CsvWriter &operator<<(T t);
private:
std::ofstream out_;
};
template <typename T>
CsvWriter &CsvWriter::operator<<(T t) {
out_ << t << ',';
return *this;
}
// This is the specialization for newlines.
// If anything of type LineBreak is passed to this function, this executes.
// *I know \b doesn't work in all cases, but it works on the only
// system where this will run so... :P*
template <>
CsvWriter &CsvWriter::operator<<(LineBreak) {
out_ << '\b' << std::endl; // Remove last comma
return *this;
}
// The constructor gives an example use of the template specialization
CsvWriter::CsvWriter(const char *fname, std::vector<const char *> headers)
: out_(fname) {
for (const char *header : headers) {
*this << header; // Prints each string in header
}
*this << BREAK; // Goes to the next line of the csv
}
Brief Explanation
This code works perfectly as is, and compiles with no complaints in gcc. However, I noticed that I was technically not allocating memory to the value BREAK
. So to check it out, I tried printing the value of &CsvWriter::BREAK
and ran into a linking error (which makes sense, I'm asking for the address of something that's not in memory). And furthermore, if I add the line CsvWriter::LineBreak CsvWriter::BREAK;
after the class definition, then I can print the value of &CsvWriter::BREAK
no problem (which also makes sense because now I've given it memory.
From this, I can puzzle together that if the value is never used in any compilation unit, the linker will never look for the value and never complain about it. But if I use it (such as grabbing the address), the linker will try and link the name, and not find anything.
Question
While I find this result very useful for what I'm trying to do, I'm curious, is this technically against the C++11 standard? Or is this perfectly valid code? If not, is there a better way to do this with a similarly clear and simple interface?
Aucun commentaire:
Enregistrer un commentaire