|
3 | 3 | */ |
4 | 4 |
|
5 | 5 | #pragma once |
| 6 | +#include <cmath> |
6 | 7 | #include <fstream> |
| 8 | +#include <initializer_list> |
7 | 9 | #include <iostream> |
8 | 10 | #include <memory> |
9 | 11 | #ifdef CSV_HAS_CXX20 |
@@ -115,54 +117,47 @@ namespace csv { |
115 | 117 | csv::enable_if_t<std::is_floating_point<T>::value, int> = 0 |
116 | 118 | > |
117 | 119 | inline std::string to_string(T value) { |
118 | | -#ifdef __clang__ |
119 | | - return std::to_string(value); |
120 | | -#else |
121 | | - // TODO: Figure out why the below code doesn't work on clang |
122 | | - std::string result = ""; |
| 120 | + std::string result = ""; |
123 | 121 |
|
124 | | - T integral_part; |
125 | | - T fractional_part = std::abs(std::modf(value, &integral_part)); |
126 | | - integral_part = std::abs(integral_part); |
| 122 | + T integral_part; |
| 123 | + T fractional_part = csv_abs(std::modf(value, &integral_part)); |
| 124 | + integral_part = csv_abs(integral_part); |
127 | 125 |
|
128 | | - // Integral part |
129 | | - if (value < 0) result = "-"; |
| 126 | + // Integral part |
| 127 | + if (value < 0) result = "-"; |
130 | 128 |
|
131 | | - if (integral_part == 0) { |
132 | | - result += "0"; |
133 | | - } |
134 | | - else { |
135 | | - for (int n_digits = num_digits(integral_part); n_digits > 0; n_digits --) { |
136 | | - int digit = (int)(std::fmod(integral_part, pow10(n_digits)) / pow10(n_digits - 1)); |
137 | | - result += (char)('0' + digit); |
138 | | - } |
| 129 | + if (integral_part == 0) { |
| 130 | + result += "0"; |
| 131 | + } |
| 132 | + else { |
| 133 | + for (int n_digits = num_digits(integral_part); n_digits > 0; n_digits --) { |
| 134 | + int digit = (int)(std::fmod(integral_part, pow10(n_digits)) / pow10(n_digits - 1)); |
| 135 | + result += (char)('0' + digit); |
139 | 136 | } |
| 137 | + } |
140 | 138 |
|
141 | | - // Decimal part |
142 | | - result += "."; |
| 139 | + // Decimal part |
| 140 | + result += "."; |
143 | 141 |
|
144 | | - if (fractional_part > 0) { |
145 | | - fractional_part *= (T)(pow10(DECIMAL_PLACES)); |
146 | | - for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) { |
147 | | - int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1)); |
148 | | - result += (char)('0' + digit); |
149 | | - } |
150 | | - } |
151 | | - else { |
152 | | - result += "0"; |
| 142 | + if (fractional_part > 0) { |
| 143 | + fractional_part *= (T)(pow10(DECIMAL_PLACES)); |
| 144 | + for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) { |
| 145 | + int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1)); |
| 146 | + result += (char)('0' + digit); |
153 | 147 | } |
| 148 | + } |
| 149 | + else { |
| 150 | + result += "0"; |
| 151 | + } |
154 | 152 |
|
155 | | - return result; |
156 | | -#endif |
| 153 | + return result; |
157 | 154 | } |
158 | 155 | } |
159 | 156 |
|
160 | 157 | /** Sets how many places after the decimal will be written for floating point numbers. */ |
161 | | -#ifndef __clang__ |
162 | 158 | inline static void set_decimal_places(int precision) { |
163 | 159 | internals::DECIMAL_PLACES = precision; |
164 | 160 | } |
165 | | -#endif |
166 | 161 |
|
167 | 162 | namespace internals { |
168 | 163 | /** SFINAE trait: detects if a type is iterable (has std::begin/end). */ |
@@ -295,6 +290,37 @@ namespace csv { |
295 | 290 | return *this; |
296 | 291 | } |
297 | 292 |
|
| 293 | + /** Write a row from a braced initializer list of string fields. |
| 294 | + * |
| 295 | + * This is the most concise syntax for all-string rows: |
| 296 | + * @code |
| 297 | + * writer << {"Name", "Age", "City"}; |
| 298 | + * writer << {"Alice", "30", "Paris"}; |
| 299 | + * @endcode |
| 300 | + * |
| 301 | + * For rows with mixed numeric and string fields, use write_row(): |
| 302 | + * @code |
| 303 | + * writer.write_row("Alice", 30, 1.75); |
| 304 | + * @endcode |
| 305 | + */ |
| 306 | + DelimWriter& operator<<(std::initializer_list<csv::string_view> record) { |
| 307 | + write_range_impl(record); |
| 308 | + return *this; |
| 309 | + } |
| 310 | + |
| 311 | + /** Write a row from a variadic argument list. |
| 312 | + * |
| 313 | + * Accepts any mix of string and numeric types: |
| 314 | + * @code |
| 315 | + * writer.write_row("Alice", 30, 1.75, "Paris"); |
| 316 | + * @endcode |
| 317 | + */ |
| 318 | + template<typename... Args> |
| 319 | + DelimWriter& write_row(Args&&... args) { |
| 320 | + this->write_tuple<0>(std::forward_as_tuple(std::forward<Args>(args)...)); |
| 321 | + return *this; |
| 322 | + } |
| 323 | + |
298 | 324 | #ifdef CSV_HAS_CXX20 |
299 | 325 | /** Write a range of string-like fields as one delimited row. |
300 | 326 | * |
|
0 commit comments