1 // Copyright 2020 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //
16 // POSIX spec:
17 // http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
18 //
19 #include "absl/strings/internal/str_format/arg.h"
20
21 #include <cassert>
22 #include <cerrno>
23 #include <cstdlib>
24 #include <string>
25 #include <type_traits>
26
27 #include "absl/base/port.h"
28 #include "absl/strings/internal/str_format/float_conversion.h"
29 #include "absl/strings/numbers.h"
30
31 namespace absl {
32 ABSL_NAMESPACE_BEGIN
33 namespace str_format_internal {
34 namespace {
35
36 // Reduce *capacity by s.size(), clipped to a 0 minimum.
ReducePadding(string_view s,size_t * capacity)37 void ReducePadding(string_view s, size_t *capacity) {
38 *capacity = Excess(s.size(), *capacity);
39 }
40
41 // Reduce *capacity by n, clipped to a 0 minimum.
ReducePadding(size_t n,size_t * capacity)42 void ReducePadding(size_t n, size_t *capacity) {
43 *capacity = Excess(n, *capacity);
44 }
45
46 template <typename T>
47 struct MakeUnsigned : std::make_unsigned<T> {};
48 template <>
49 struct MakeUnsigned<absl::int128> {
50 using type = absl::uint128;
51 };
52 template <>
53 struct MakeUnsigned<absl::uint128> {
54 using type = absl::uint128;
55 };
56
57 template <typename T>
58 struct IsSigned : std::is_signed<T> {};
59 template <>
60 struct IsSigned<absl::int128> : std::true_type {};
61 template <>
62 struct IsSigned<absl::uint128> : std::false_type {};
63
64 // Integral digit printer.
65 // Call one of the PrintAs* routines after construction once.
66 // Use with_neg_and_zero/without_neg_or_zero/is_negative to access the results.
67 class IntDigits {
68 public:
69 // Print the unsigned integer as octal.
70 // Supports unsigned integral types and uint128.
71 template <typename T>
PrintAsOct(T v)72 void PrintAsOct(T v) {
73 static_assert(!IsSigned<T>::value, "");
74 char *p = storage_ + sizeof(storage_);
75 do {
76 *--p = static_cast<char>('0' + (static_cast<size_t>(v) & 7));
77 v >>= 3;
78 } while (v);
79 start_ = p;
80 size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
81 }
82
83 // Print the signed or unsigned integer as decimal.
84 // Supports all integral types.
85 template <typename T>
PrintAsDec(T v)86 void PrintAsDec(T v) {
87 static_assert(std::is_integral<T>::value, "");
88 start_ = storage_;
89 size_ = static_cast<size_t>(numbers_internal::FastIntToBuffer(v, storage_) -
90 storage_);
91 }
92
PrintAsDec(int128 v)93 void PrintAsDec(int128 v) {
94 auto u = static_cast<uint128>(v);
95 bool add_neg = false;
96 if (v < 0) {
97 add_neg = true;
98 u = uint128{} - u;
99 }
100 PrintAsDec(u, add_neg);
101 }
102
PrintAsDec(uint128 v,bool add_neg=false)103 void PrintAsDec(uint128 v, bool add_neg = false) {
104 // This function can be sped up if needed. We can call FastIntToBuffer
105 // twice, or fix FastIntToBuffer to support uint128.
106 char *p = storage_ + sizeof(storage_);
107 do {
108 p -= 2;
109 numbers_internal::PutTwoDigits(static_cast<size_t>(v % 100), p);
110 v /= 100;
111 } while (v);
112 if (p[0] == '0') {
113 // We printed one too many hexits.
114 ++p;
115 }
116 if (add_neg) {
117 *--p = '-';
118 }
119 size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
120 start_ = p;
121 }
122
123 // Print the unsigned integer as hex using lowercase.
124 // Supports unsigned integral types and uint128.
125 template <typename T>
PrintAsHexLower(T v)126 void PrintAsHexLower(T v) {
127 static_assert(!IsSigned<T>::value, "");
128 char *p = storage_ + sizeof(storage_);
129
130 do {
131 p -= 2;
132 constexpr const char* table = numbers_internal::kHexTable;
133 std::memcpy(p, table + 2 * (static_cast<size_t>(v) & 0xFF), 2);
134 if (sizeof(T) == 1) break;
135 v >>= 8;
136 } while (v);
137 if (p[0] == '0') {
138 // We printed one too many digits.
139 ++p;
140 }
141 start_ = p;
142 size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
143 }
144
145 // Print the unsigned integer as hex using uppercase.
146 // Supports unsigned integral types and uint128.
147 template <typename T>
PrintAsHexUpper(T v)148 void PrintAsHexUpper(T v) {
149 static_assert(!IsSigned<T>::value, "");
150 char *p = storage_ + sizeof(storage_);
151
152 // kHexTable is only lowercase, so do it manually for uppercase.
153 do {
154 *--p = "0123456789ABCDEF"[static_cast<size_t>(v) & 15];
155 v >>= 4;
156 } while (v);
157 start_ = p;
158 size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
159 }
160
161 // The printed value including the '-' sign if available.
162 // For inputs of value `0`, this will return "0"
with_neg_and_zero() const163 string_view with_neg_and_zero() const { return {start_, size_}; }
164
165 // The printed value not including the '-' sign.
166 // For inputs of value `0`, this will return "".
without_neg_or_zero() const167 string_view without_neg_or_zero() const {
168 static_assert('-' < '0', "The check below verifies both.");
169 size_t advance = start_[0] <= '0' ? 1 : 0;
170 return {start_ + advance, size_ - advance};
171 }
172
is_negative() const173 bool is_negative() const { return start_[0] == '-'; }
174
175 private:
176 const char *start_;
177 size_t size_;
178 // Max size: 128 bit value as octal -> 43 digits, plus sign char
179 char storage_[128 / 3 + 1 + 1];
180 };
181
182 // Note: 'o' conversions do not have a base indicator, it's just that
183 // the '#' flag is specified to modify the precision for 'o' conversions.
BaseIndicator(const IntDigits & as_digits,const FormatConversionSpecImpl conv)184 string_view BaseIndicator(const IntDigits &as_digits,
185 const FormatConversionSpecImpl conv) {
186 // always show 0x for %p.
187 bool alt = conv.has_alt_flag() ||
188 conv.conversion_char() == FormatConversionCharInternal::p;
189 bool hex = (conv.conversion_char() == FormatConversionCharInternal::x ||
190 conv.conversion_char() == FormatConversionCharInternal::X ||
191 conv.conversion_char() == FormatConversionCharInternal::p);
192 // From the POSIX description of '#' flag:
193 // "For x or X conversion specifiers, a non-zero result shall have
194 // 0x (or 0X) prefixed to it."
195 if (alt && hex && !as_digits.without_neg_or_zero().empty()) {
196 return conv.conversion_char() == FormatConversionCharInternal::X ? "0X"
197 : "0x";
198 }
199 return {};
200 }
201
SignColumn(bool neg,const FormatConversionSpecImpl conv)202 string_view SignColumn(bool neg, const FormatConversionSpecImpl conv) {
203 if (conv.conversion_char() == FormatConversionCharInternal::d ||
204 conv.conversion_char() == FormatConversionCharInternal::i) {
205 if (neg) return "-";
206 if (conv.has_show_pos_flag()) return "+";
207 if (conv.has_sign_col_flag()) return " ";
208 }
209 return {};
210 }
211
ConvertCharImpl(char v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)212 bool ConvertCharImpl(char v,
213 const FormatConversionSpecImpl conv,
214 FormatSinkImpl* sink) {
215 size_t fill = 0;
216 if (conv.width() >= 0)
217 fill = static_cast<size_t>(conv.width());
218 ReducePadding(1, &fill);
219 if (!conv.has_left_flag()) sink->Append(fill, ' ');
220 sink->Append(1, v);
221 if (conv.has_left_flag()) sink->Append(fill, ' ');
222 return true;
223 }
224
ConvertIntImplInnerSlow(const IntDigits & as_digits,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)225 bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
226 const FormatConversionSpecImpl conv,
227 FormatSinkImpl *sink) {
228 // Print as a sequence of Substrings:
229 // [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
230 size_t fill = 0;
231 if (conv.width() >= 0)
232 fill = static_cast<size_t>(conv.width());
233
234 string_view formatted = as_digits.without_neg_or_zero();
235 ReducePadding(formatted, &fill);
236
237 string_view sign = SignColumn(as_digits.is_negative(), conv);
238 ReducePadding(sign, &fill);
239
240 string_view base_indicator = BaseIndicator(as_digits, conv);
241 ReducePadding(base_indicator, &fill);
242
243 bool precision_specified = conv.precision() >= 0;
244 size_t precision =
245 precision_specified ? static_cast<size_t>(conv.precision()) : size_t{1};
246
247 if (conv.has_alt_flag() &&
248 conv.conversion_char() == FormatConversionCharInternal::o) {
249 // From POSIX description of the '#' (alt) flag:
250 // "For o conversion, it increases the precision (if necessary) to
251 // force the first digit of the result to be zero."
252 if (formatted.empty() || *formatted.begin() != '0') {
253 size_t needed = formatted.size() + 1;
254 precision = std::max(precision, needed);
255 }
256 }
257
258 size_t num_zeroes = Excess(formatted.size(), precision);
259 ReducePadding(num_zeroes, &fill);
260
261 size_t num_left_spaces = !conv.has_left_flag() ? fill : 0;
262 size_t num_right_spaces = conv.has_left_flag() ? fill : 0;
263
264 // From POSIX description of the '0' (zero) flag:
265 // "For d, i, o, u, x, and X conversion specifiers, if a precision
266 // is specified, the '0' flag is ignored."
267 if (!precision_specified && conv.has_zero_flag()) {
268 num_zeroes += num_left_spaces;
269 num_left_spaces = 0;
270 }
271
272 sink->Append(num_left_spaces, ' ');
273 sink->Append(sign);
274 sink->Append(base_indicator);
275 sink->Append(num_zeroes, '0');
276 sink->Append(formatted);
277 sink->Append(num_right_spaces, ' ');
278 return true;
279 }
280
281 template <typename T,
282 typename std::enable_if<(std::is_integral<T>::value &&
283 std::is_signed<T>::value) ||
284 std::is_same<T, int128>::value,
285 int>::type = 0>
ConvertV(T)286 constexpr auto ConvertV(T) {
287 return FormatConversionCharInternal::d;
288 }
289
290 template <typename T,
291 typename std::enable_if<(std::is_integral<T>::value &&
292 std::is_unsigned<T>::value) ||
293 std::is_same<T, uint128>::value,
294 int>::type = 0>
ConvertV(T)295 constexpr auto ConvertV(T) {
296 return FormatConversionCharInternal::u;
297 }
298
299 template <typename T>
ConvertFloatArg(T v,FormatConversionSpecImpl conv,FormatSinkImpl * sink)300 bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
301 if (conv.conversion_char() == FormatConversionCharInternal::v) {
302 conv.set_conversion_char(FormatConversionCharInternal::g);
303 }
304
305 return FormatConversionCharIsFloat(conv.conversion_char()) &&
306 ConvertFloatImpl(v, conv, sink);
307 }
308
ConvertStringArg(string_view v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)309 inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
310 FormatSinkImpl *sink) {
311 if (conv.is_basic()) {
312 sink->Append(v);
313 return true;
314 }
315 return sink->PutPaddedString(v, conv.width(), conv.precision(),
316 conv.has_left_flag());
317 }
318
319 } // namespace
320
ConvertBoolArg(bool v,FormatSinkImpl * sink)321 bool ConvertBoolArg(bool v, FormatSinkImpl *sink) {
322 if (v) {
323 sink->Append("true");
324 } else {
325 sink->Append("false");
326 }
327 return true;
328 }
329
330 template <typename T>
ConvertIntArg(T v,FormatConversionSpecImpl conv,FormatSinkImpl * sink)331 bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
332 using U = typename MakeUnsigned<T>::type;
333 IntDigits as_digits;
334
335 if (conv.conversion_char() == FormatConversionCharInternal::v) {
336 conv.set_conversion_char(ConvertV(T{}));
337 }
338
339 // This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes
340 // it to complain about a switch/case type mismatch, even though both are
341 // FormatConverionChar. Likely this is because at this point
342 // FormatConversionChar is declared, but not defined.
343 switch (static_cast<uint8_t>(conv.conversion_char())) {
344 case static_cast<uint8_t>(FormatConversionCharInternal::c):
345 return ConvertCharImpl(static_cast<char>(v), conv, sink);
346
347 case static_cast<uint8_t>(FormatConversionCharInternal::o):
348 as_digits.PrintAsOct(static_cast<U>(v));
349 break;
350
351 case static_cast<uint8_t>(FormatConversionCharInternal::x):
352 as_digits.PrintAsHexLower(static_cast<U>(v));
353 break;
354 case static_cast<uint8_t>(FormatConversionCharInternal::X):
355 as_digits.PrintAsHexUpper(static_cast<U>(v));
356 break;
357
358 case static_cast<uint8_t>(FormatConversionCharInternal::u):
359 as_digits.PrintAsDec(static_cast<U>(v));
360 break;
361
362 case static_cast<uint8_t>(FormatConversionCharInternal::d):
363 case static_cast<uint8_t>(FormatConversionCharInternal::i):
364 as_digits.PrintAsDec(v);
365 break;
366
367 case static_cast<uint8_t>(FormatConversionCharInternal::a):
368 case static_cast<uint8_t>(FormatConversionCharInternal::e):
369 case static_cast<uint8_t>(FormatConversionCharInternal::f):
370 case static_cast<uint8_t>(FormatConversionCharInternal::g):
371 case static_cast<uint8_t>(FormatConversionCharInternal::A):
372 case static_cast<uint8_t>(FormatConversionCharInternal::E):
373 case static_cast<uint8_t>(FormatConversionCharInternal::F):
374 case static_cast<uint8_t>(FormatConversionCharInternal::G):
375 return ConvertFloatImpl(static_cast<double>(v), conv, sink);
376
377 default:
378 ABSL_ASSUME(false);
379 }
380
381 if (conv.is_basic()) {
382 sink->Append(as_digits.with_neg_and_zero());
383 return true;
384 }
385 return ConvertIntImplInnerSlow(as_digits, conv, sink);
386 }
387
388 template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv,
389 FormatSinkImpl *sink);
390 template bool ConvertIntArg<signed char>(signed char v,
391 FormatConversionSpecImpl conv,
392 FormatSinkImpl *sink);
393 template bool ConvertIntArg<unsigned char>(unsigned char v,
394 FormatConversionSpecImpl conv,
395 FormatSinkImpl *sink);
396 template bool ConvertIntArg<short>(short v, // NOLINT
397 FormatConversionSpecImpl conv,
398 FormatSinkImpl *sink);
399 template bool ConvertIntArg<unsigned short>(unsigned short v, // NOLINT
400 FormatConversionSpecImpl conv,
401 FormatSinkImpl *sink);
402 template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv,
403 FormatSinkImpl *sink);
404 template bool ConvertIntArg<unsigned int>(unsigned int v,
405 FormatConversionSpecImpl conv,
406 FormatSinkImpl *sink);
407 template bool ConvertIntArg<long>(long v, // NOLINT
408 FormatConversionSpecImpl conv,
409 FormatSinkImpl *sink);
410 template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT
411 FormatConversionSpecImpl conv,
412 FormatSinkImpl *sink);
413 template bool ConvertIntArg<long long>(long long v, // NOLINT
414 FormatConversionSpecImpl conv,
415 FormatSinkImpl *sink);
416 template bool ConvertIntArg<unsigned long long>(unsigned long long v, // NOLINT
417 FormatConversionSpecImpl conv,
418 FormatSinkImpl *sink);
419
420 // ==================== Strings ====================
FormatConvertImpl(const std::string & v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)421 StringConvertResult FormatConvertImpl(const std::string &v,
422 const FormatConversionSpecImpl conv,
423 FormatSinkImpl *sink) {
424 return {ConvertStringArg(v, conv, sink)};
425 }
426
FormatConvertImpl(string_view v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)427 StringConvertResult FormatConvertImpl(string_view v,
428 const FormatConversionSpecImpl conv,
429 FormatSinkImpl *sink) {
430 return {ConvertStringArg(v, conv, sink)};
431 }
432
433 ArgConvertResult<FormatConversionCharSetUnion(
434 FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
FormatConvertImpl(const char * v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)435 FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv,
436 FormatSinkImpl *sink) {
437 if (conv.conversion_char() == FormatConversionCharInternal::p)
438 return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
439 size_t len;
440 if (v == nullptr) {
441 len = 0;
442 } else if (conv.precision() < 0) {
443 len = std::strlen(v);
444 } else {
445 // If precision is set, we look for the NUL-terminator on the valid range.
446 len = static_cast<size_t>(std::find(v, v + conv.precision(), '\0') - v);
447 }
448 return {ConvertStringArg(string_view(v, len), conv, sink)};
449 }
450
451 // ==================== Raw pointers ====================
FormatConvertImpl(VoidPtr v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)452 ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
453 VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
454 if (!v.value) {
455 sink->Append("(nil)");
456 return {true};
457 }
458 IntDigits as_digits;
459 as_digits.PrintAsHexLower(v.value);
460 return {ConvertIntImplInnerSlow(as_digits, conv, sink)};
461 }
462
463 // ==================== Floats ====================
FormatConvertImpl(float v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)464 FloatingConvertResult FormatConvertImpl(float v,
465 const FormatConversionSpecImpl conv,
466 FormatSinkImpl *sink) {
467 return {ConvertFloatArg(v, conv, sink)};
468 }
FormatConvertImpl(double v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)469 FloatingConvertResult FormatConvertImpl(double v,
470 const FormatConversionSpecImpl conv,
471 FormatSinkImpl *sink) {
472 return {ConvertFloatArg(v, conv, sink)};
473 }
FormatConvertImpl(long double v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)474 FloatingConvertResult FormatConvertImpl(long double v,
475 const FormatConversionSpecImpl conv,
476 FormatSinkImpl *sink) {
477 return {ConvertFloatArg(v, conv, sink)};
478 }
479
480 // ==================== Chars ====================
FormatConvertImpl(char v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)481 CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv,
482 FormatSinkImpl *sink) {
483 return {ConvertIntArg(v, conv, sink)};
484 }
FormatConvertImpl(signed char v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)485 CharConvertResult FormatConvertImpl(signed char v,
486 const FormatConversionSpecImpl conv,
487 FormatSinkImpl *sink) {
488 return {ConvertIntArg(v, conv, sink)};
489 }
FormatConvertImpl(unsigned char v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)490 CharConvertResult FormatConvertImpl(unsigned char v,
491 const FormatConversionSpecImpl conv,
492 FormatSinkImpl *sink) {
493 return {ConvertIntArg(v, conv, sink)};
494 }
495
496 // ==================== Ints ====================
FormatConvertImpl(short v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)497 IntegralConvertResult FormatConvertImpl(short v, // NOLINT
498 const FormatConversionSpecImpl conv,
499 FormatSinkImpl *sink) {
500 return {ConvertIntArg(v, conv, sink)};
501 }
FormatConvertImpl(unsigned short v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)502 IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
503 const FormatConversionSpecImpl conv,
504 FormatSinkImpl *sink) {
505 return {ConvertIntArg(v, conv, sink)};
506 }
FormatConvertImpl(int v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)507 IntegralConvertResult FormatConvertImpl(int v,
508 const FormatConversionSpecImpl conv,
509 FormatSinkImpl *sink) {
510 return {ConvertIntArg(v, conv, sink)};
511 }
FormatConvertImpl(unsigned v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)512 IntegralConvertResult FormatConvertImpl(unsigned v,
513 const FormatConversionSpecImpl conv,
514 FormatSinkImpl *sink) {
515 return {ConvertIntArg(v, conv, sink)};
516 }
FormatConvertImpl(long v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)517 IntegralConvertResult FormatConvertImpl(long v, // NOLINT
518 const FormatConversionSpecImpl conv,
519 FormatSinkImpl *sink) {
520 return {ConvertIntArg(v, conv, sink)};
521 }
FormatConvertImpl(unsigned long v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)522 IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
523 const FormatConversionSpecImpl conv,
524 FormatSinkImpl *sink) {
525 return {ConvertIntArg(v, conv, sink)};
526 }
FormatConvertImpl(long long v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)527 IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
528 const FormatConversionSpecImpl conv,
529 FormatSinkImpl *sink) {
530 return {ConvertIntArg(v, conv, sink)};
531 }
FormatConvertImpl(unsigned long long v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)532 IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
533 const FormatConversionSpecImpl conv,
534 FormatSinkImpl *sink) {
535 return {ConvertIntArg(v, conv, sink)};
536 }
FormatConvertImpl(absl::int128 v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)537 IntegralConvertResult FormatConvertImpl(absl::int128 v,
538 const FormatConversionSpecImpl conv,
539 FormatSinkImpl *sink) {
540 return {ConvertIntArg(v, conv, sink)};
541 }
FormatConvertImpl(absl::uint128 v,const FormatConversionSpecImpl conv,FormatSinkImpl * sink)542 IntegralConvertResult FormatConvertImpl(absl::uint128 v,
543 const FormatConversionSpecImpl conv,
544 FormatSinkImpl *sink) {
545 return {ConvertIntArg(v, conv, sink)};
546 }
547
548 ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_();
549
550
551
552 } // namespace str_format_internal
553
554 ABSL_NAMESPACE_END
555 } // namespace absl
556