| // { dg-options "-std=gnu++20" } |
| // { dg-do run { target c++20 } } |
| |
| #include <format> |
| |
| #ifndef __cpp_lib_format |
| # error "Feature test macro for std::format is missing in <format>" |
| #elif __cpp_lib_format < 202106L |
| # error "Feature test macro for std::format has wrong value in <format>" |
| #endif |
| |
| #undef __cpp_lib_format |
| #include <version> |
| #ifndef __cpp_lib_format |
| # error "Feature test macro for std::format is missing in <version>" |
| #elif __cpp_lib_format < 202106L |
| # error "Feature test macro for std::format has wrong value in <version>" |
| #endif |
| |
| #include <string> |
| #include <limits> |
| #include <cstdint> |
| #include <cstdio> |
| #include <testsuite_hooks.h> |
| |
| void |
| test_no_args() |
| { |
| std::string s; |
| s = std::format("disco"); |
| VERIFY( s == "disco" ); |
| |
| s = std::format("}} machine {{ funk }} specialists {{"); |
| VERIFY( s == "} machine { funk } specialists {" ); |
| |
| s = std::format("128bpm }}"); |
| VERIFY( s == "128bpm }" ); |
| } |
| |
| void |
| test_unescaped() |
| { |
| #ifdef __cpp_exceptions |
| for (auto f : { "{", "}", "{{{", "{{}", "}{", "{{{{{" }) |
| try { |
| (void) std::vformat(f, std::make_format_args()); |
| VERIFY( false ); |
| } catch (const std::format_error& e) { |
| std::string what = e.what(); |
| VERIFY( what.find("unmatched") != what.npos ); |
| } |
| #endif |
| } |
| |
| struct brit_punc : std::numpunct<char> |
| { |
| std::string do_grouping() const override { return "\3\3"; } |
| char do_thousands_sep() const override { return ','; } |
| std::string do_truename() const override { return "yes mate"; } |
| std::string do_falsename() const override { return "nah bruv"; } |
| }; |
| |
| void |
| test_std_examples() |
| { |
| using namespace std; |
| |
| string s = format("{0}-{{", 8); // value of s is "8-{" |
| VERIFY( s == "8-{" ); |
| |
| // align |
| { |
| char c = 120; |
| string s0 = format("{:6}", 42); |
| VERIFY(s0 == " 42"); |
| string s1 = format("{:6}", 'x'); |
| VERIFY(s1 == "x "); |
| string s2 = format("{:*<6}", 'x'); |
| VERIFY(s2 == "x*****"); |
| string s3 = format("{:*>6}", 'x'); |
| VERIFY(s3 == "*****x"); |
| string s4 = format("{:*^6}", 'x'); |
| VERIFY(s4 == "**x***"); |
| string s5 = format("{:6d}", c); |
| VERIFY(s5 == " 120"); |
| string s6 = format("{:6}", true); |
| VERIFY(s6 == "true "); |
| } |
| |
| // sign |
| { |
| double inf = numeric_limits<double>::infinity(); |
| double nan = numeric_limits<double>::quiet_NaN(); |
| string s0 = format("{0:},{0:+},{0:-},{0: }", 1); |
| VERIFY(s0 == "1,+1,1, 1"); |
| string s1 = format("{0:},{0:+},{0:-},{0: }", -1); |
| VERIFY(s1 == "-1,-1,-1,-1"); |
| string s2 = format("{0:},{0:+},{0:-},{0: }", inf); |
| VERIFY(s2 == "inf,+inf,inf, inf"); |
| string s3 = format("{0:},{0:+},{0:-},{0: }", nan); |
| VERIFY(s3 == "nan,+nan,nan, nan"); |
| } |
| |
| // alternate form and zero fill |
| { |
| char c = 120; |
| string s1 = format("{:+06d}", c); |
| VERIFY(s1 == "+00120"); |
| string s2 = format("{:#06x}", 0xa); |
| VERIFY(s2 == "0x000a"); |
| string s3 = format("{:<06}", -42); |
| VERIFY(s3 == "-42 "); // 0 is ignored because of < alignment |
| } |
| |
| // integer presentation types |
| { |
| // Change global locale so "{:L}" adds digit separators. |
| std::locale::global(std::locale({}, new brit_punc)); |
| |
| string s0 = format("{}", 42); |
| VERIFY(s0 == "42"); |
| string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); |
| VERIFY(s1 == "101010 42 52 2a"); |
| string s2 = format("{0:#x} {0:#X}", 42); |
| VERIFY(s2 == "0x2a 0X2A"); |
| string s3 = format("{:L}", 1234); |
| VERIFY(s3 == "1,234"); |
| |
| // Test locale's "byte-and-a-half" grouping (Imperial word? tribble?). |
| string s4 = format("{:#Lx}", 0xfffff); |
| VERIFY(s4 == "0xff,fff"); |
| |
| // Restore |
| std::locale::global(std::locale::classic()); |
| } |
| } |
| |
| void |
| test_alternate_forms() |
| { |
| std::string s; |
| |
| s = std::format("{0:#b} {0:+#B} {0:#o} {0:#x} {0:+#X} {0: #d}", 42); |
| VERIFY( s == "0b101010 +0B101010 052 0x2a +0X2A 42" ); |
| s = std::format("{0:#b} {0:+#B} {0:#o} {0:#x} {0:+#X} {0: #d}", 0); |
| VERIFY( s == "0b0 +0B0 0 0x0 +0X0 0" ); |
| |
| s = std::format("{0:+#012g} {0:+#014g} {0:+#014g}", 1234.0); |
| VERIFY( s == "+00001234.00 +0000001234.00 +0000001234.00" ); |
| s = std::format("{0:+#0{1}g} {0:+#0{2}g} {0:+#0{2}g}", 1234.5, 12, 14); |
| VERIFY( s == "+00001234.50 +0000001234.50 +0000001234.50" ); |
| |
| s = std::format("{:#.2g}", -0.0); |
| VERIFY( s == "-0.0" ); |
| } |
| |
| struct euro_punc : std::numpunct<char> |
| { |
| std::string do_grouping() const override { return "\3\3"; } |
| char do_thousands_sep() const override { return '.'; } |
| char do_decimal_point() const override { return ','; } |
| }; |
| |
| void |
| test_locale() |
| { |
| // The default C locale. |
| std::locale cloc = std::locale::classic(); |
| // A custom locale using comma digit separators. |
| std::locale bloc(cloc, new brit_punc); |
| // A custom locale using period digit separators. |
| std::locale eloc(cloc, new euro_punc); |
| |
| std::string s; |
| |
| // Change the global locale: |
| std::locale::global(bloc); |
| // Format using the global locale: |
| s = std::format("{0:L} {0:Lx} {0:Lb}", 12345); |
| VERIFY( s == "12,345 3,039 11,000,000,111,001" ); |
| s = std::format("{0:L} {0:.7Lg} {0:La}", 12345.6789); |
| VERIFY( s == "12,345.6789 12,345.68 1.81cd6e631f8a1p+13" ); |
| |
| s = std::format("{0:s} {0:L} {1:Ls} {0:Ld}", true, false); |
| VERIFY( s == "true yes mate nah bruv 1" ); |
| |
| // Format using a specific locale: |
| s = std::format(eloc, "{0:L} {0:Lx} {0:Lb}", 12345); |
| VERIFY( s == "12.345 3.039 11.000.000.111.001" ); |
| s = std::format(eloc, "{0:L} {0:.7LG} {0:La}", 12345.6789); |
| VERIFY( s == "12.345,6789 12.345,68 1,81cd6e631f8a1p+13" ); |
| |
| s = std::format(eloc, "{0:#Lg} {0:+#.3Lg} {0:#08.4Lg}", -1234.); |
| VERIFY( s == "-1.234,00 -1,23e+03 -01.234," ); |
| |
| // Restore |
| std::locale::global(cloc); |
| } |
| |
| void |
| test_width() |
| { |
| std::string s; |
| |
| s = std::format("{:4}", ""); |
| VERIFY( s == " " ); |
| s = std::format("{:{}}", "", 3); |
| VERIFY( s == " " ); |
| s = std::format("{:{}}|{:{}}", 1, 2, 3, 4); |
| VERIFY( s == " 1| 3" ); |
| s = std::format("{1:{0}}", 2, ""); |
| VERIFY( s == " " ); |
| s = std::format("{:03}", 9); |
| VERIFY( s == "009" ); |
| |
| s = std::format("DR {0:{1}}: allow width {1} from arg-id", 3721, 0); |
| VERIFY( s == "DR 3721: allow width 0 from arg-id" ); |
| |
| try { |
| s = std::format("Negative width is an error: {0:{1}}", 123, -1); |
| VERIFY(false); |
| } catch (const std::format_error&) { |
| } |
| |
| try { |
| auto args = std::make_format_args(false, true); |
| s = std::vformat("DR 3720: restrict type of width arg-id {0:{1}}", args); |
| VERIFY(false); |
| } catch (const std::format_error&) { |
| } |
| |
| try { |
| auto args = std::make_format_args('?', '!'); |
| s = std::vformat("DR 3720: restrict type of width arg-id {0:{1}}", args); |
| VERIFY(false); |
| } catch (const std::format_error&) { |
| } |
| } |
| |
| void |
| test_wchar() |
| { |
| using namespace std::literals; |
| std::wstring s; |
| |
| s = std::format(L"{} {} {} {} {} {}", L'0', 1, 2LL, 3.4, L"five", L"six"s); |
| VERIFY( s == L"0 1 2 3.4 five six" ); |
| |
| std::locale loc; |
| s = std::format(loc, L"{:L} {:.3s}{:Lc}", true, L"data"sv, '.'); |
| VERIFY( s == L"true dat." ); |
| } |
| |
| void |
| test_minmax() |
| { |
| auto check = []<typename T, typename U = std::make_unsigned_t<T>>(T, U = 0) { |
| const int digits = std::numeric_limits<T>::digits; |
| const std::string zeros(digits, '0'); |
| const std::string ones(digits, '1'); |
| auto s = std::format("{:b}" , std::numeric_limits<T>::min()); |
| VERIFY( s == "-1" + zeros ); |
| s = std::format("{:b}" , std::numeric_limits<T>::max()); |
| VERIFY( s == ones ); |
| s = std::format("{:0{}b}" , std::numeric_limits<U>::min(), digits + 1); |
| VERIFY( s == '0' + zeros ); |
| s = std::format("{:b}" , std::numeric_limits<U>::max()); |
| VERIFY( s == '1' + ones ); |
| }; |
| check(std::int8_t(0)); |
| check(std::int16_t(0)); |
| check(std::int32_t(0)); |
| check(std::int64_t(0)); |
| #ifdef __SIZEOF_INT128__ |
| // std::make_unsigned_t<__int128> is invalid for strict -std=c++20 mode, |
| // so pass a second argument of the unsigned type. |
| check(__int128(0), (unsigned __int128)(0)); |
| #endif |
| } |
| |
| void |
| test_p1652r1() // printf corner cases in std::format |
| { |
| std::string s; |
| |
| // Problem 1: "#o" specification should not print 0 as "00" |
| s = std::format("{:#o}", 0); |
| VERIFY( s == "0" ); |
| |
| // Problem 2: 'c' should be able to print 65 as "A" (ASCII) |
| int c = 'A'; |
| s = std::format("{:c}", c); |
| VERIFY( s == "A" ); |
| |
| // Problem 3: "-000nan" is not a floating point value |
| double nan = std::numeric_limits<double>::quiet_NaN(); |
| try { |
| s = std::vformat("{:0=6}", std::make_format_args(nan)); |
| VERIFY( false ); |
| } catch (const std::format_error&) { |
| } |
| |
| s = std::format("{:06}", nan); |
| VERIFY( s == " nan" ); |
| |
| // Problem 4: bool needs a type format specifier |
| s = std::format("{:s}", true); |
| VERIFY( s == "true" ); |
| |
| // Problem 5: double does not roundtrip float |
| s = std::format("{}", 3.31f); |
| VERIFY( s == "3.31" ); |
| } |
| |
| template<typename T> |
| bool format_float() |
| { |
| auto s = std::format("{:#} != {:<+7.3f}", (T)-0.0, (T)0.5); |
| return s == "-0. != +0.500 "; |
| } |
| |
| #if __cplusplus > 202002L |
| template<typename T> |
| concept formattable = std::formattable<T, char>; |
| #else |
| template<typename T> |
| concept formattable = requires (T t, char* p) { std::to_chars(p, p, t); }; |
| #endif |
| |
| void |
| test_float128() |
| { |
| #ifdef __SIZEOF_FLOAT128__ |
| if constexpr (formattable<__float128>) |
| VERIFY( format_float<__float128>() ); |
| else |
| std::puts("Cannot format __float128 on this target"); |
| #endif |
| #if __FLT128_DIG__ |
| if constexpr (formattable<_Float128>) |
| VERIFY( format_float<_Float128>() ); |
| else |
| std::puts("Cannot format _Float128 on this target"); |
| #endif |
| } |
| |
| void |
| test_pointer() |
| { |
| void* p = nullptr; |
| const void* pc = p; |
| std::string s, str_int; |
| |
| s = std::format("{} {} {}", p, pc, nullptr); |
| VERIFY( s == "0x0 0x0 0x0" ); |
| s = std::format("{:p} {:p} {:p}", p, pc, nullptr); |
| VERIFY( s == "0x0 0x0 0x0" ); |
| s = std::format("{:4},{:5},{:6}", p, pc, nullptr); // width |
| VERIFY( s == " 0x0, 0x0, 0x0" ); |
| s = std::format("{:<4},{:>5},{:^7}", p, pc, nullptr); // align+width |
| VERIFY( s == "0x0 , 0x0, 0x0 " ); |
| s = std::format("{:o<4},{:o>5},{:o^7}", p, pc, nullptr); // fill+align+width |
| VERIFY( s == "0x0o,oo0x0,oo0x0oo" ); |
| |
| pc = p = &s; |
| str_int = std::format("{:#x}", reinterpret_cast<std::uintptr_t>(p)); |
| s = std::format("{} {} {}", p, pc, nullptr); |
| VERIFY( s == (str_int + ' ' + str_int + " 0x0") ); |
| str_int = std::format("{:#20x}", reinterpret_cast<std::uintptr_t>(p)); |
| s = std::format("{:20} {:20p}", p, pc); |
| VERIFY( s == (str_int + ' ' + str_int) ); |
| |
| #if __cplusplus > 202302L || ! defined __STRICT_ANSI__ |
| // P2510R3 Formatting pointers |
| s = std::format("{:06} {:07P} {:08p}", (void*)0, (const void*)0, nullptr); |
| VERIFY( s == "0x0000 0X00000 0x000000" ); |
| str_int = std::format("{:#016x}", reinterpret_cast<std::uintptr_t>(p)); |
| s = std::format("{:016} {:016}", p, pc); |
| VERIFY( s == (str_int + ' ' + str_int) ); |
| str_int = std::format("{:#016X}", reinterpret_cast<std::uintptr_t>(p)); |
| s = std::format("{:016P} {:016P}", p, pc); |
| VERIFY( s == (str_int + ' ' + str_int) ); |
| #endif |
| } |
| |
| int main() |
| { |
| test_no_args(); |
| test_unescaped(); |
| test_std_examples(); |
| test_alternate_forms(); |
| test_locale(); |
| test_width(); |
| test_wchar(); |
| test_minmax(); |
| test_p1652r1(); |
| test_float128(); |
| test_pointer(); |
| } |