25 static int DECIMAL_PLACES = 5;
29 CSV_CONST CONSTEXPR_14
30 long double pow10(
const T& n)
noexcept {
31 static_assert(std::is_integral<T>::value,
"pow10 only supports integral exponents");
33 long double multiplicand = n > 0 ? 10 : 0.1,
35 T iterations = n > 0 ? n : -n;
37 for (T i = 0; i < iterations; i++) {
45 CSV_CONST CONSTEXPR_14
46 long double pow10(
const unsigned& n)
noexcept {
47 long double multiplicand = n > 0 ? 10 : 0.1,
50 for (
unsigned i = 0; i < n; i++) {
60 template<
typename T =
int>
66 inline int csv_abs(
int x) {
71 inline long int csv_abs(
long int x) {
76 inline long long int csv_abs(
long long int x) {
86 inline double csv_abs(
double x) {
91 inline long double csv_abs(
long double x) {
100 csv::enable_if_t<std::is_arithmetic<T>::value,
int> = 0
106 for (; x >= 1; digits++)
109 return (x == 0) ? 1 : digits;
114 csv::enable_if_t<std::is_unsigned<T>::value,
int> = 0>
116 std::string digits_reverse =
"";
117 if (value == 0)
return "0";
119 for (; value > 0; value /= 10)
120 digits_reverse += (
char)(
'0' + (value % 10));
122 return std::string(digits_reverse.rbegin(), digits_reverse.rend());
128 csv::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value,
int> = 0
130 inline std::string to_string(T value) {
131 return (value >= 0) ? to_string((
size_t)value)
132 :
"-" + to_string((size_t)(value * -1));
138 csv::enable_if_t<std::is_floating_point<T>::value,
int> = 0
140 inline std::string to_string(T value) {
141 std::string result =
"";
143 long double integral_part;
144 long double fractional_part = csv_abs(std::modf((
long double)value, &integral_part));
146 const long double scale = pow10(DECIMAL_PLACES);
147 long double rounded_fractional = std::round(fractional_part * scale);
151 long double abs_integral = csv_abs(integral_part);
154 if (rounded_fractional >= scale) {
156 rounded_fractional = 0;
160 if (value < 0) result =
"-";
162 if (abs_integral == 0) {
166 for (
int n_digits =
num_digits(abs_integral); n_digits > 0; n_digits --) {
167 int digit = (int)(std::fmod(abs_integral,
pow10(n_digits)) /
pow10(n_digits - 1));
168 result += (char)(
'0' + digit);
175 if (rounded_fractional > 0) {
176 for (
int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
177 int digit = (int)(std::fmod(rounded_fractional,
pow10(n_digits)) /
pow10(n_digits - 1));
178 result += (char)(
'0' + digit);
190inline static void set_decimal_places(
int precision) {
191 internals::DECIMAL_PLACES = precision;
196 template<
typename T,
typename =
void>
203 static auto test(
int) ->
decltype(
204 std::begin(std::declval<const U&>()),
205 std::end(std::declval<const U&>()),
209 static std::false_type test(...);
211 static constexpr bool value =
decltype(test<T>(0))::value;
215 template<
typename T,
typename =
void>
219 struct is_tuple<T, typename std::enable_if<true>::type> {
222 static auto test(
int) ->
decltype(std::tuple_size<U>::value, std::true_type{});
224 static std::false_type test(...);
226 static constexpr bool value =
decltype(test<T>(0))::value;
254 template<
class OutputStream,
char Delim,
char Quote>
260 : out(&_out), quote_minimal(_quote_minimal) {}
263 template<
typename T = OutputStream,
264 csv::enable_if_t<std::is_same<T, std::ofstream>::value,
int> = 0>
265 DelimWriter(
const std::string& filename,
bool _quote_minimal =
true)
266 : owned_out(new std::ofstream(filename, std::ios::out)),
267 out(owned_out.get()),
268 quote_minimal(_quote_minimal) {
269 if (!owned_out->is_open())
270 internals::throw_failed_open_for_writing(filename);
277 : owned_out(std::move(other.owned_out)),
279 quote_minimal(other.quote_minimal),
280 auto_flush_(other.auto_flush_),
281 batch_buffer_(std::move(other.batch_buffer_)) {
283 out = owned_out.get();
286 other.quote_minimal =
true;
289 DelimWriter& operator=(DelimWriter&& other)
noexcept {
290 if (
this == &other)
return *
this;
292 owned_out = std::move(other.owned_out);
294 quote_minimal = other.quote_minimal;
295 auto_flush_ = other.auto_flush_;
296 batch_buffer_ = std::move(other.batch_buffer_);
299 out = owned_out.get();
303 other.quote_minimal =
true;
309 this->auto_flush_ = value;
315 this->auto_flush_ = value;
316 return std::move(*
this);
321 return this->auto_flush_;
333 template<
typename T,
size_t N>
335 write_range_impl(record);
340 template<
typename T,
size_t N>
342 write_range_impl(record);
358 auto write_row(T&& record) ->
decltype(*this << std::forward<T>(record)) {
359 return *this << std::forward<T>(record);
371 template<
typename T,
typename U,
typename... Rest>
373 this->write_tuple<0>(std::forward_as_tuple(
374 std::forward<T>(first), std::forward<U>(second), std::forward<Rest>(rest)...));
391 template<std::ranges::input_range Rows>
394 for (
auto&& row : rows) {
395 append_row_like(row);
396 flush_batch_if_needed();
409 template<std::ranges::input_range Range>
411 requires std::ranges::input_range<Range>
412 && std::convertible_to<std::ranges::range_reference_t<Range>,
csv::string_view> {
413 write_range_impl(container);
418 template<
typename RowLike>
422 append_row_like(row);
435 template<
typename Range>
436 typename std::enable_if<
439 && !std::is_same<Range, std::string>::value
440 && !std::is_same<Range, csv::string_view>::value,
443 write_range_impl(record);
449 template<
typename... T>
451 this->write_tuple<0, T...>(record);
467 template<
typename Range>
468 inline void append_range_fields(Range&& record) {
469 auto it = std::begin(record);
470 auto end = std::end(record);
477 for (; it != end; ++it) {
478 batch_buffer_.push_back(Delim);
485 size_t pos = internals::find_next_non_special(in, 0, simd_sentinels_);
487 for (; pos < in.size(); ++pos) {
489 if (ch == Quote || ch == Delim || ch ==
'\r' || ch ==
'\n')
499 template<
typename Range>
500 inline void write_range_impl(
const Range& record) {
501 append_range_fields(record);
508 template<
typename Row>
509 void append_row_like(Row&& row) {
511 append_range_fields(std::forward<Row>(row));
514 append_range_fields(row.to_sv_range());
524 !std::is_convertible<T, std::string>::value
525 && !std::is_convertible<T, csv::string_view>::value
528 void write_field(T in) {
529 const std::string serialized = internals::to_string(in);
530 write_raw(serialized);
536 std::is_convertible<T, std::string>::value
537 || std::is_convertible<T, csv::string_view>::value
540 void write_field(T in) {
541 IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) {
542 write_escaped_field(in);
545 const std::string serialized(in);
546 write_escaped_field(serialized);
552 batch_buffer_.append(in.data(), in.size());
556 const size_t first_special = find_first_special_for_writer(in);
558 if (first_special == in.size()) {
559 if (!quote_minimal) {
560 batch_buffer_.push_back(Quote);
562 batch_buffer_.push_back(Quote);
569 write_quoted_field(in, first_special);
573 batch_buffer_.push_back(Quote);
575 size_t chunk_start = 0;
576 size_t pos = first_special;
577 while (pos < in.size()) {
578 if (in[pos] == Quote) {
579 write_raw(in.substr(chunk_start, pos - chunk_start));
580 batch_buffer_.push_back(Quote);
581 batch_buffer_.push_back(Quote);
582 chunk_start = pos + 1;
588 write_raw(in.substr(chunk_start));
589 batch_buffer_.push_back(Quote);
593 template<
size_t Index = 0,
typename... T>
594 typename std::enable_if<Index <
sizeof...(T),
void>::type write_tuple(
const std::tuple<T...>& record) {
595 write_field(std::get<Index>(record));
597 CSV_MSVC_PUSH_DISABLE(4127)
598 IF_CONSTEXPR (Index + 1 < sizeof...(T)) batch_buffer_.push_back(Delim);
601 this->write_tuple<Index + 1>(record);
605 template<
size_t Index = 0, typename... T>
606 typename std::enable_if<Index == sizeof...(T),
void>::type write_tuple(const std::tuple<T...>& record) {
614 batch_buffer_.push_back(
'\n');
617 void finish_write_call() {
618 if (this->auto_flush_) {
624 flush_batch_if_needed();
628 if (batch_buffer_.empty())
return;
630 out->write(batch_buffer_.data(),
static_cast<std::streamsize
>(batch_buffer_.size()));
631 batch_buffer_.clear();
634 void flush_batch_if_needed() {
635 if (batch_buffer_.size() >= batch_flush_threshold_)
644 std::unique_ptr<OutputStream> owned_out;
650 bool auto_flush_ =
true;
651 static constexpr size_t batch_flush_threshold_ = 64 * 1024;
652 std::string batch_buffer_;
653 internals::SentinelVecs simd_sentinels_{Delim, Quote};
663 template<
class OutputStream>
674 template<
class OutputStream>
678 template<
class OutputStream>
679 inline CSVWriter<OutputStream>
make_csv_writer(OutputStream& out,
bool quote_minimal=
true) {
680 return CSVWriter<OutputStream>(out, quote_minimal);
684 template<
class OutputStream>
685 inline TSVWriter<OutputStream>
make_tsv_writer(OutputStream& out,
bool quote_minimal=
true) {
686 return TSVWriter<OutputStream>(out, quote_minimal);
SIMD-accelerated skip for runs of non-special CSV bytes.
Class for writing delimiter separated values files.
DelimWriter & write_row(T &&first, U &&second, Rest &&... rest)
Write a row from a variadic list of mixed-type values.
~DelimWriter()
Destructor will flush remaining data.
DelimWriter & operator<<(const std::array< T, N > &record)
Write a std::array of strings as one delimited row.
DelimWriter && set_auto_flush(bool value) &&noexcept
Configure whether each write operation flushes the underlying stream.
DelimWriter & set_auto_flush(bool value) &noexcept
Configure whether each write operation flushes the underlying stream.
DelimWriter(const std::string &filename, bool _quote_minimal=true)
Construct a DelimWriter that owns an output file stream.
auto write_row(T &&record) -> decltype(*this<< std::forward< T >(record))
Write a row from any single argument accepted by operator<< (std::vector, std::array,...
DelimWriter & write_rows(Rows &&rows)
Write many rows using a shared batch buffer.
DelimWriter & operator<<(const T(&record)[N])
Write a C-style array of strings as one delimited row.
DelimWriter & operator<<(const RowLike &row)
Write a row-like object that exposes to_sv_range().
bool get_auto_flush() const noexcept
Return whether each write operation flushes the underlying stream.
DelimWriter & operator<<(const std::tuple< T... > &record)
Write a C-style array of strings as one delimited row.
void flush()
Flushes the written data.
DelimWriter(OutputStream &_out, bool _quote_minimal=true)
Construct a DelimWriter over the specified output stream.
DelimWriter & operator<<(Range &&container)
Write a range of string-like fields as one delimited row.
A standalone header file containing shared code.
#define IF_CONSTEXPR
Expands to if constexpr in C++17 and if otherwise.
Shared exception message templates and throw helpers.
T csv_abs(T x)
Calculate the absolute value of a number.
CSV_CONST CONSTEXPR_14 long double pow10(const T &n) noexcept
Compute 10 to the power of an integral exponent.
int num_digits(T x)
Calculate the number of digits in a number.
std::string to_string(T value)
to_string() for unsigned integers
The all encompassing namespace.
TSVWriter< OutputStream > make_tsv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::TSVWriter over the output stream.
CSVWriter< OutputStream > make_csv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::CSVWriter over the output stream.
DelimWriter< OutputStream, ',', '"'> CSVWriter
An alias for csv::DelimWriter for writing standard CSV files.
DelimWriter< OutputStream, '\t', '"'> TSVWriter
Class for writing tab-separated values files.
std::string_view string_view
The string_view class used by this library.
SFINAE trait: detects if a type is iterable (has std::begin/end).
SFINAE trait: detects if a type is a std::tuple.