samedi 29 octobre 2016

Using static class variables without allocationg them

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