From 6b018edfb9f6221738b753245e5818bc651792af Mon Sep 17 00:00:00 2001 From: Kareem Otoum <88245299+KareemOtoum@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:39:02 +0000 Subject: [PATCH] add support for positional arguments as width and precision specifiers (#4643) * make positional arguments work as width and precision specifiers with floating-point formats too * fix test case failing ci tests --- include/fmt/printf.h | 34 ++++++++++++++++++++++++++++++---- test/printf-test.cc | 29 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/include/fmt/printf.h b/include/fmt/printf.h index cc066d8c..5285d79c 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -357,8 +357,21 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs, if (specs.width == -1) report_error("number is too big"); } else if (*it == '*') { ++it; - specs.width = static_cast( - get_arg(-1).visit(detail::printf_width_handler(specs))); + // Check for positional width argument like *1$ + if (it != end && *it >= '0' && *it <= '9') { + int width_index = parse_nonnegative_int(it, end, -1); + if (it != end && *it == '$') { + ++it; + specs.width = static_cast( + get_arg(width_index).visit(detail::printf_width_handler(specs))); + } else { + // Invalid format, rewind and treat as non-positional + report_error("invalid format specifier"); + } + } else { + specs.width = static_cast( + get_arg(-1).visit(detail::printf_width_handler(specs))); + } } } return arg_index; @@ -439,8 +452,21 @@ void vprintf(buffer& buf, basic_string_view format, specs.precision = parse_nonnegative_int(it, end, 0); } else if (c == '*') { ++it; - specs.precision = - static_cast(get_arg(-1).visit(printf_precision_handler())); + // Check for positional precision argument like .*1$ + if (it != end && *it >= '0' && *it <= '9') { + int precision_index = parse_nonnegative_int(it, end, -1); + if (it != end && *it == '$') { + ++it; + specs.precision = static_cast( + get_arg(precision_index).visit(printf_precision_handler())); + } else { + // Invalid format, rewind and treat as non-positional + report_error("invalid format specifier"); + } + } else { + specs.precision = + static_cast(get_arg(-1).visit(printf_precision_handler())); + } } else { specs.precision = 0; } diff --git a/test/printf-test.cc b/test/printf-test.cc index 277a3b67..63035006 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -296,6 +296,35 @@ TEST(printf_test, dynamic_precision) { } } +TEST(printf_test, positional_width) { + EXPECT_EQ(" 42", test_sprintf("%2$*1$d", 5, 42)); + EXPECT_EQ("42 ", test_sprintf("%2$*1$d", -5, 42)); + EXPECT_EQ(" abc", test_sprintf("%2$*1$s", 5, "abc")); + EXPECT_THROW_MSG(test_sprintf("%2$*1$d", 5.0, 42), format_error, + "width is not integer"); + EXPECT_THROW_MSG(test_sprintf("%2$*1$d"), format_error, "argument not found"); + EXPECT_THROW_MSG(test_sprintf("%2$*1$d", big_num, 42), format_error, + "number is too big"); +} + +TEST(printf_test, positional_precision) { + EXPECT_EQ("00042", test_sprintf("%2$.*1$d", 5, 42)); + EXPECT_EQ("42", test_sprintf("%2$.*1$d", -5, 42)); + EXPECT_EQ("Hell", test_sprintf("%2$.*1$s", 4, "Hello")); + EXPECT_THROW_MSG(test_sprintf("%2$.*1$d", 5.0, 42), format_error, + "precision is not integer"); + EXPECT_THROW_MSG(test_sprintf("%2$.*1$d"), format_error, "argument not found"); + EXPECT_THROW_MSG(test_sprintf("%2$.*1$d", big_num, 42), format_error, + "number is too big"); +} + +TEST(printf_test, positional_width_and_precision) { + EXPECT_EQ(" 00042", test_sprintf("%3$*1$.*2$d", 7, 5, 42)); + EXPECT_EQ(" ab", test_sprintf("%3$*1$.*2$s", 7, 2, "abcdef")); + EXPECT_EQ(" 00042", test_sprintf("%3$*1$.*2$x", 7, 5, 0x42)); + EXPECT_EQ("100.4400000", test_sprintf("%6$-*5$.*4$f%3$s%2$s%1$s", "", "", "", 7, 4, 100.44)); +} + template struct make_signed { using type = T; };