1 // -*- C++ -*- 2 //===----------------------------------------------------------------------===// 3 // 4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5 // See https://llvm.org/LICENSE.txt for license information. 6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7 // 8 //===----------------------------------------------------------------------===// 9 10 #ifndef _LIBCPP___FORMAT_PARSER_STD_FORMAT_SPEC_H 11 #define _LIBCPP___FORMAT_PARSER_STD_FORMAT_SPEC_H 12 13 #include <__config> 14 #include <__debug> 15 #include <__format/format_arg.h> 16 #include <__format/format_error.h> 17 #include <__format/format_string.h> 18 #include <__variant/monostate.h> 19 #include <concepts> 20 #include <cstdint> 21 #include <type_traits> 22 23 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) 24 # pragma GCC system_header 25 #endif 26 27 _LIBCPP_BEGIN_NAMESPACE_STD 28 29 #if _LIBCPP_STD_VER > 17 30 31 // TODO FMT Remove this once we require compilers with proper C++20 support. 32 // If the compiler has no concepts support, the format header will be disabled. 33 // Without concepts support enable_if needs to be used and that too much effort 34 // to support compilers with partial C++20 support. 35 # if !defined(_LIBCPP_HAS_NO_CONCEPTS) 36 37 namespace __format_spec { 38 39 /** 40 * Contains the flags for the std-format-spec. 41 * 42 * Some format-options can only be used for specific C++types and may depend on 43 * the selected format-type. 44 * * The C++type filtering can be done using the proper policies for 45 * @ref __parser_std. 46 * * The format-type filtering needs to be done post parsing in the parser 47 * derived from @ref __parser_std. 48 */ 49 class _LIBCPP_TYPE_VIS _Flags { 50 public: 51 enum class _LIBCPP_ENUM_VIS _Alignment : uint8_t { 52 /** 53 * No alignment is set in the format string. 54 * 55 * Zero-padding is ignored when an alignment is selected. 56 * The default alignment depends on the selected format-type. 57 */ 58 __default, 59 __left, 60 __center, 61 __right 62 }; 63 enum class _LIBCPP_ENUM_VIS _Sign : uint8_t { 64 /** 65 * No sign is set in the format string. 66 * 67 * The sign isn't allowed for certain format-types. By using this value 68 * it's possible to detect whether or not the user explicitly set the sign 69 * flag. For formatting purposes it behaves the same as @ref __minus. 70 */ 71 __default, 72 __minus, 73 __plus, 74 __space 75 }; 76 77 _Alignment __alignment : 2 {_Alignment::__default}; 78 _Sign __sign : 2 {_Sign::__default}; 79 uint8_t __alternate_form : 1 {false}; 80 uint8_t __zero_padding : 1 {false}; 81 uint8_t __locale_specific_form : 1 {false}; 82 83 enum class _LIBCPP_ENUM_VIS _Type : uint8_t { 84 __default, 85 __string, 86 __binary_lower_case, 87 __binary_upper_case, 88 __octal, 89 __decimal, 90 __hexadecimal_lower_case, 91 __hexadecimal_upper_case, 92 __pointer, 93 __char, 94 __float_hexadecimal_lower_case, 95 __float_hexadecimal_upper_case, 96 __scientific_lower_case, 97 __scientific_upper_case, 98 __fixed_lower_case, 99 __fixed_upper_case, 100 __general_lower_case, 101 __general_upper_case 102 }; 103 104 _Type __type{_Type::__default}; 105 }; 106 107 namespace __detail { 108 template <class _CharT> 109 _LIBCPP_HIDE_FROM_ABI constexpr bool 110 __parse_alignment(_CharT __c, _Flags& __flags) noexcept { 111 switch (__c) { 112 case _CharT('<'): 113 __flags.__alignment = _Flags::_Alignment::__left; 114 return true; 115 116 case _CharT('^'): 117 __flags.__alignment = _Flags::_Alignment::__center; 118 return true; 119 120 case _CharT('>'): 121 __flags.__alignment = _Flags::_Alignment::__right; 122 return true; 123 } 124 return false; 125 } 126 } // namespace __detail 127 128 template <class _CharT> 129 class _LIBCPP_TEMPLATE_VIS __parser_fill_align { 130 public: 131 // TODO FMT The standard doesn't specify this character is a Unicode 132 // character. Validate what fmt and MSVC have implemented. 133 _CharT __fill{_CharT(' ')}; 134 135 protected: 136 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 137 __parse(const _CharT* __begin, const _CharT* __end, _Flags& __flags) { 138 _LIBCPP_ASSERT(__begin != __end, 139 "When called with an empty input the function will cause " 140 "undefined behavior by evaluating data not in the input"); 141 if (__begin + 1 != __end) { 142 if (__detail::__parse_alignment(*(__begin + 1), __flags)) { 143 if (*__begin == _CharT('{') || *__begin == _CharT('}')) 144 __throw_format_error( 145 "The format-spec fill field contains an invalid character"); 146 __fill = *__begin; 147 return __begin + 2; 148 } 149 } 150 151 if (__detail::__parse_alignment(*__begin, __flags)) 152 return __begin + 1; 153 154 return __begin; 155 } 156 }; 157 158 template <class _CharT> 159 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 160 __parse_sign(const _CharT* __begin, _Flags& __flags) noexcept { 161 switch (*__begin) { 162 case _CharT('-'): 163 __flags.__sign = _Flags::_Sign::__minus; 164 break; 165 case _CharT('+'): 166 __flags.__sign = _Flags::_Sign::__plus; 167 break; 168 case _CharT(' '): 169 __flags.__sign = _Flags::_Sign::__space; 170 break; 171 default: 172 return __begin; 173 } 174 return __begin + 1; 175 } 176 177 template <class _CharT> 178 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 179 __parse_alternate_form(const _CharT* __begin, _Flags& __flags) noexcept { 180 if (*__begin == _CharT('#')) { 181 __flags.__alternate_form = true; 182 ++__begin; 183 } 184 185 return __begin; 186 } 187 188 template <class _CharT> 189 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 190 __parse_zero_padding(const _CharT* __begin, _Flags& __flags) noexcept { 191 if (*__begin == _CharT('0')) { 192 __flags.__zero_padding = true; 193 ++__begin; 194 } 195 196 return __begin; 197 } 198 199 template <class _CharT> 200 _LIBCPP_HIDE_FROM_ABI constexpr __format::__parse_number_result< _CharT> 201 __parse_arg_id(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) { 202 // This function is a wrapper to call the real parser. But it does the 203 // validation for the pre-conditions and post-conditions. 204 if (__begin == __end) 205 __throw_format_error("End of input while parsing format-spec arg-id"); 206 207 __format::__parse_number_result __r = 208 __format::__parse_arg_id(__begin, __end, __parse_ctx); 209 210 if (__r.__ptr == __end || *__r.__ptr != _CharT('}')) 211 __throw_format_error("A format-spec arg-id should terminate at a '}'"); 212 213 ++__r.__ptr; 214 return __r; 215 } 216 217 template <class _Context> 218 _LIBCPP_HIDE_FROM_ABI constexpr uint32_t 219 __substitute_arg_id(basic_format_arg<_Context> __arg) { 220 return visit_format_arg( 221 [](auto __arg) -> uint32_t { 222 using _Type = decltype(__arg); 223 if constexpr (integral<_Type>) { 224 if constexpr (signed_integral<_Type>) { 225 if (__arg < 0) 226 __throw_format_error("A format-spec arg-id replacement shouldn't " 227 "have a negative value"); 228 } 229 230 using _CT = common_type_t<_Type, decltype(__format::__number_max)>; 231 if (static_cast<_CT>(__arg) > 232 static_cast<_CT>(__format::__number_max)) 233 __throw_format_error("A format-spec arg-id replacement exceeds " 234 "the maximum supported value"); 235 return __arg; 236 } else if constexpr (same_as<_Type, monostate>) 237 __throw_format_error("Argument index out of bounds"); 238 else 239 __throw_format_error("A format-spec arg-id replacement argument " 240 "isn't an integral type"); 241 }, 242 __arg); 243 } 244 245 class _LIBCPP_TYPE_VIS __parser_width { 246 public: 247 /** Contains a width or an arg-id. */ 248 uint32_t __width : 31 {0}; 249 /** Determines whether the value stored is a width or an arg-id. */ 250 uint32_t __width_as_arg : 1 {0}; 251 252 protected: 253 /** 254 * Does the supplied std-format-spec contain a width field? 255 * 256 * When the field isn't present there's no padding required. This can be used 257 * to optimize the formatting. 258 */ 259 constexpr bool __has_width_field() const noexcept { 260 return __width_as_arg || __width; 261 } 262 263 /** 264 * Does the supplied width field contain an arg-id? 265 * 266 * If @c true the formatter needs to call @ref __substitute_width_arg_id. 267 */ 268 constexpr bool __width_needs_substitution() const noexcept { 269 return __width_as_arg; 270 } 271 272 template <class _CharT> 273 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 274 __parse(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) { 275 if (*__begin == _CharT('0')) 276 __throw_format_error( 277 "A format-spec width field shouldn't have a leading zero"); 278 279 if (*__begin == _CharT('{')) { 280 __format::__parse_number_result __r = 281 __parse_arg_id(++__begin, __end, __parse_ctx); 282 __width = __r.__value; 283 __width_as_arg = 1; 284 return __r.__ptr; 285 } 286 287 if (*__begin < _CharT('0') || *__begin > _CharT('9')) 288 return __begin; 289 290 __format::__parse_number_result __r = 291 __format::__parse_number(__begin, __end); 292 __width = __r.__value; 293 _LIBCPP_ASSERT(__width != 0, 294 "A zero value isn't allowed and should be impossible, " 295 "due to validations in this function"); 296 return __r.__ptr; 297 } 298 299 void _LIBCPP_HIDE_FROM_ABI constexpr __substitute_width_arg_id(auto __arg) { 300 _LIBCPP_ASSERT(__width_as_arg == 1, 301 "Substitute width called when no substitution is required"); 302 303 // The clearing of the flag isn't required but looks better when debugging 304 // the code. 305 __width_as_arg = 0; 306 __width = __substitute_arg_id(__arg); 307 if (__width == 0) 308 __throw_format_error( 309 "A format-spec width field replacement should have a positive value"); 310 } 311 }; 312 313 class _LIBCPP_TYPE_VIS __parser_precision { 314 public: 315 /** Contains a precision or an arg-id. */ 316 uint32_t __precision : 31 {__format::__number_max}; 317 /** 318 * Determines whether the value stored is a precision or an arg-id. 319 * 320 * @note Since @ref __precision == @ref __format::__number_max is a valid 321 * value, the default value contains an arg-id of INT32_MAX. (This number of 322 * arguments isn't supported by compilers.) This is used to detect whether 323 * the std-format-spec contains a precision field. 324 */ 325 uint32_t __precision_as_arg : 1 {1}; 326 327 protected: 328 /** 329 * Does the supplied std-format-spec contain a precision field? 330 * 331 * When the field isn't present there's no truncating required. This can be 332 * used to optimize the formatting. 333 */ 334 constexpr bool __has_precision_field() const noexcept { 335 336 return __precision_as_arg == 0 || // Contains a value? 337 __precision != __format::__number_max; // The arg-id is valid? 338 } 339 340 /** 341 * Does the supplied precision field contain an arg-id? 342 * 343 * If @c true the formatter needs to call @ref __substitute_precision_arg_id. 344 */ 345 constexpr bool __precision_needs_substitution() const noexcept { 346 return __precision_as_arg && __precision != __format::__number_max; 347 } 348 349 template <class _CharT> 350 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 351 __parse(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) { 352 if (*__begin != _CharT('.')) 353 return __begin; 354 355 ++__begin; 356 if (__begin == __end) 357 __throw_format_error("End of input while parsing format-spec precision"); 358 359 if (*__begin == _CharT('0')) { 360 ++__begin; 361 if (__begin != __end && *__begin >= '0' && *__begin <= '9') 362 __throw_format_error( 363 "A format-spec precision field shouldn't have a leading zero"); 364 365 __precision = 0; 366 __precision_as_arg = 0; 367 return __begin; 368 } 369 370 if (*__begin == _CharT('{')) { 371 __format::__parse_number_result __arg_id = 372 __parse_arg_id(++__begin, __end, __parse_ctx); 373 _LIBCPP_ASSERT(__arg_id.__value != __format::__number_max, 374 "Unsupported number of arguments, since this number of " 375 "arguments is used a special value"); 376 __precision = __arg_id.__value; 377 return __arg_id.__ptr; 378 } 379 380 if (*__begin < _CharT('0') || *__begin > _CharT('9')) 381 __throw_format_error( 382 "The format-spec precision field doesn't contain a value or arg-id"); 383 384 __format::__parse_number_result __r = 385 __format::__parse_number(__begin, __end); 386 __precision = __r.__value; 387 __precision_as_arg = 0; 388 return __r.__ptr; 389 } 390 391 void _LIBCPP_HIDE_FROM_ABI constexpr __substitute_precision_arg_id( 392 auto __arg) { 393 _LIBCPP_ASSERT( 394 __precision_as_arg == 1 && __precision != __format::__number_max, 395 "Substitute precision called when no substitution is required"); 396 397 // The clearing of the flag isn't required but looks better when debugging 398 // the code. 399 __precision_as_arg = 0; 400 __precision = __substitute_arg_id(__arg); 401 } 402 }; 403 404 template <class _CharT> 405 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 406 __parse_locale_specific_form(const _CharT* __begin, _Flags& __flags) noexcept { 407 if (*__begin == _CharT('L')) { 408 __flags.__locale_specific_form = true; 409 ++__begin; 410 } 411 412 return __begin; 413 } 414 415 template <class _CharT> 416 _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* 417 __parse_type(const _CharT* __begin, _Flags& __flags) { 418 419 // Determines the type. It does not validate whether the selected type is 420 // valid. Most formatters have optional fields that are only allowed for 421 // certain types. These parsers need to do validation after the type has 422 // been parsed. So its easier to implement the validation for all types in 423 // the specific parse function. 424 switch (*__begin) { 425 case 'A': 426 __flags.__type = _Flags::_Type::__float_hexadecimal_upper_case; 427 break; 428 case 'B': 429 __flags.__type = _Flags::_Type::__binary_upper_case; 430 break; 431 case 'E': 432 __flags.__type = _Flags::_Type::__scientific_upper_case; 433 break; 434 case 'F': 435 __flags.__type = _Flags::_Type::__fixed_upper_case; 436 break; 437 case 'G': 438 __flags.__type = _Flags::_Type::__general_upper_case; 439 break; 440 case 'X': 441 __flags.__type = _Flags::_Type::__hexadecimal_upper_case; 442 break; 443 case 'a': 444 __flags.__type = _Flags::_Type::__float_hexadecimal_lower_case; 445 break; 446 case 'b': 447 __flags.__type = _Flags::_Type::__binary_lower_case; 448 break; 449 case 'c': 450 __flags.__type = _Flags::_Type::__char; 451 break; 452 case 'd': 453 __flags.__type = _Flags::_Type::__decimal; 454 break; 455 case 'e': 456 __flags.__type = _Flags::_Type::__scientific_lower_case; 457 break; 458 case 'f': 459 __flags.__type = _Flags::_Type::__fixed_lower_case; 460 break; 461 case 'g': 462 __flags.__type = _Flags::_Type::__general_lower_case; 463 break; 464 case 'o': 465 __flags.__type = _Flags::_Type::__octal; 466 break; 467 case 'p': 468 __flags.__type = _Flags::_Type::__pointer; 469 break; 470 case 's': 471 __flags.__type = _Flags::_Type::__string; 472 break; 473 case 'x': 474 __flags.__type = _Flags::_Type::__hexadecimal_lower_case; 475 break; 476 default: 477 return __begin; 478 } 479 return ++__begin; 480 } 481 482 /** 483 * The parser for the std-format-spec. 484 * 485 * [format.string.std]/1 specifies the std-format-spec: 486 * fill-and-align sign # 0 width precision L type 487 * 488 * All these fields are optional. Whether these fields can be used depend on: 489 * - The type supplied to the format string. 490 * E.g. A string never uses the sign field so the field may not be set. 491 * This constrain is validated by the parsers in this file. 492 * - The supplied value for the optional type field. 493 * E.g. A int formatted as decimal uses the sign field. 494 * When formatted as a char the sign field may no longer be set. 495 * This constrain isn't validated by the parsers in this file. 496 * 497 * The base classes are ordered to minimize the amount of padding. 498 * 499 * This implements the parser for the string types. 500 */ 501 template <class _CharT> 502 class _LIBCPP_TEMPLATE_VIS __parser_string 503 : public __parser_width, // provides __width(|as_arg) 504 public __parser_precision, // provides __precision(|as_arg) 505 public __parser_fill_align<_CharT>, // provides __fill and uses __flags 506 public _Flags // provides __flags 507 { 508 public: 509 using char_type = _CharT; 510 511 _LIBCPP_HIDE_FROM_ABI constexpr __parser_string() { 512 this->__alignment = _Flags::_Alignment::__left; 513 } 514 515 /** 516 * The low-level std-format-spec parse function. 517 * 518 * @pre __begin points at the beginning of the std-format-spec. This means 519 * directly after the ':'. 520 * @pre The std-format-spec parses the entire input, or the first unmatched 521 * character is a '}'. 522 * 523 * @returns The iterator pointing at the last parsed character. 524 */ 525 _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx) 526 -> decltype(__parse_ctx.begin()) { 527 auto __it = __parse(__parse_ctx); 528 __process_display_type(); 529 return __it; 530 } 531 532 private: 533 /** 534 * Parses the std-format-spec. 535 * 536 * @throws __throw_format_error When @a __parse_ctx contains an ill-formed 537 * std-format-spec. 538 * 539 * @returns An iterator to the end of input or point at the closing '}'. 540 */ 541 _LIBCPP_HIDE_FROM_ABI constexpr auto __parse(auto& __parse_ctx) 542 -> decltype(__parse_ctx.begin()) { 543 544 auto __begin = __parse_ctx.begin(); 545 auto __end = __parse_ctx.end(); 546 if (__begin == __end) 547 return __begin; 548 549 __begin = __parser_fill_align<_CharT>::__parse(__begin, __end, 550 static_cast<_Flags&>(*this)); 551 if (__begin == __end) 552 return __begin; 553 554 __begin = __parser_width::__parse(__begin, __end, __parse_ctx); 555 if (__begin == __end) 556 return __begin; 557 558 __begin = __parser_precision::__parse(__begin, __end, __parse_ctx); 559 if (__begin == __end) 560 return __begin; 561 562 __begin = __parse_type(__begin, static_cast<_Flags&>(*this)); 563 564 if (__begin != __end && *__begin != _CharT('}')) 565 __throw_format_error( 566 "The format-spec should consume the input or end with a '}'"); 567 568 return __begin; 569 } 570 571 /** Processes the parsed std-format-spec based on the parsed display type. */ 572 void _LIBCPP_HIDE_FROM_ABI constexpr __process_display_type() { 573 switch (this->__type) { 574 case _Flags::_Type::__default: 575 case _Flags::_Type::__string: 576 break; 577 578 default: 579 __throw_format_error("The format-spec type has a type not supported for " 580 "a string argument"); 581 } 582 } 583 }; 584 585 /** 586 * The parser for the std-format-spec. 587 * 588 * This implements the parser for the integral types. This includes the 589 * character type and boolean type. 590 * 591 * See @ref __parser_string. 592 */ 593 template <class _CharT> 594 class _LIBCPP_TEMPLATE_VIS __parser_integral 595 : public __parser_width, // provides __width(|as_arg) 596 public __parser_fill_align<_CharT>, // provides __fill and uses __flags 597 public _Flags // provides __flags 598 { 599 public: 600 using char_type = _CharT; 601 602 // TODO FMT This class probably doesn't need public member functions after 603 // format.string.std/std_format_spec_integral.pass.cpp has been retired. 604 605 /** 606 * The low-level std-format-spec parse function. 607 * 608 * @pre __begin points at the beginning of the std-format-spec. This means 609 * directly after the ':'. 610 * @pre The std-format-spec parses the entire input, or the first unmatched 611 * character is a '}'. 612 * 613 * @returns The iterator pointing at the last parsed character. 614 */ 615 _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx) 616 -> decltype(__parse_ctx.begin()) { 617 auto __begin = __parse_ctx.begin(); 618 auto __end = __parse_ctx.end(); 619 if (__begin == __end) 620 return __begin; 621 622 __begin = __parser_fill_align<_CharT>::__parse(__begin, __end, 623 static_cast<_Flags&>(*this)); 624 if (__begin == __end) 625 return __begin; 626 627 __begin = __parse_sign(__begin, static_cast<_Flags&>(*this)); 628 if (__begin == __end) 629 return __begin; 630 631 __begin = __parse_alternate_form(__begin, static_cast<_Flags&>(*this)); 632 if (__begin == __end) 633 return __begin; 634 635 __begin = __parse_zero_padding(__begin, static_cast<_Flags&>(*this)); 636 if (__begin == __end) 637 return __begin; 638 639 __begin = __parser_width::__parse(__begin, __end, __parse_ctx); 640 if (__begin == __end) 641 return __begin; 642 643 __begin = 644 __parse_locale_specific_form(__begin, static_cast<_Flags&>(*this)); 645 if (__begin == __end) 646 return __begin; 647 648 __begin = __parse_type(__begin, static_cast<_Flags&>(*this)); 649 650 if (__begin != __end && *__begin != _CharT('}')) 651 __throw_format_error( 652 "The format-spec should consume the input or end with a '}'"); 653 654 return __begin; 655 } 656 657 protected: 658 /** 659 * Handles the post-parsing updates for the integer types. 660 * 661 * Updates the zero-padding and alignment for integer types. 662 * 663 * [format.string.std]/13 664 * If the 0 character and an align option both appear, the 0 character is 665 * ignored. 666 * 667 * For the formatter a @ref __default alignment means zero-padding. Update 668 * the alignment based on parsed format string. 669 */ 670 _LIBCPP_HIDE_FROM_ABI constexpr void __handle_integer() noexcept { 671 this->__zero_padding &= this->__alignment == _Flags::_Alignment::__default; 672 if (!this->__zero_padding && 673 this->__alignment == _Flags::_Alignment::__default) 674 this->__alignment = _Flags::_Alignment::__right; 675 } 676 677 /** 678 * Handles the post-parsing updates for the character types. 679 * 680 * Sets the alignment and validates the format flags set for a character type. 681 * 682 * At the moment the validation for a character and a Boolean behave the 683 * same, but this may change in the future. 684 * Specifically at the moment the locale-specific form is allowed for the 685 * char output type, but it has no effect on the output. 686 */ 687 _LIBCPP_HIDE_FROM_ABI constexpr void __handle_char() { __handle_bool(); } 688 689 /** 690 * Handles the post-parsing updates for the Boolean types. 691 * 692 * Sets the alignment and validates the format flags set for a Boolean type. 693 */ 694 _LIBCPP_HIDE_FROM_ABI constexpr void __handle_bool() { 695 if (this->__sign != _Flags::_Sign::__default) 696 __throw_format_error("A sign field isn't allowed in this format-spec"); 697 698 if (this->__alternate_form) 699 __throw_format_error( 700 "An alternate form field isn't allowed in this format-spec"); 701 702 if (this->__zero_padding) 703 __throw_format_error( 704 "A zero-padding field isn't allowed in this format-spec"); 705 706 if (this->__alignment == _Flags::_Alignment::__default) 707 this->__alignment = _Flags::_Alignment::__left; 708 } 709 }; 710 711 // TODO FMT Add a parser for floating-point values. 712 // TODO FMT Add a parser for pointer values. 713 714 } // namespace __format_spec 715 716 # endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) 717 718 #endif //_LIBCPP_STD_VER > 17 719 720 _LIBCPP_END_NAMESPACE_STD 721 722 #endif // _LIBCPP___FORMAT_PARSER_STD_FORMAT_SPEC_H 723