From adb4f149c0655c3d12e033cff11f5830b5234bf9 Mon Sep 17 00:00:00 2001 From: NgoQuocViet2001 Date: Sat, 24 Oct 2026 22:39:30 +0700 Subject: [PATCH 1/2] Fix generic path format when formatting to wide strings get_path_string used the native or generic string computed by the path formatter only when converting wchar_t to UTF-8 and fell back to p.string() otherwise, so the 'g' specifier was ignored when the output character type matched path::value_type. On Windows, fmt::format(L"{:g}", path(L"C:\\foo")) returned L"C:\\foo" instead of L"C:/foo". Use the passed string in that case, as write_escaped_path already does. --- include/fmt/std.h | 2 ++ test/std-test.cc | 3 +++ 2 files changed, 5 insertions(+) diff --git a/include/fmt/std.h b/include/fmt/std.h index 2bab00f2..a8f7c9f1 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -107,6 +107,8 @@ auto get_path_string(const std::filesystem::path& p, if constexpr (std::is_same_v && std::is_same_v) { return to_utf8(native, to_utf8_error_policy::wtf); + } else if constexpr (std::is_same_v) { + return native; } else { return p.string(); } diff --git a/test/std-test.cc b/test/std-test.cc index eaf238b4..32f77e55 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -14,6 +14,7 @@ #include "fmt/os.h" // fmt::system_category #include "fmt/ranges.h" +#include "fmt/xchar.h" #include "gtest-extra.h" // StartsWith #ifdef __cpp_lib_filesystem @@ -35,6 +36,8 @@ TEST(std_test, path) { # ifdef _WIN32 EXPECT_EQ(fmt::format("{}", path("C:\\foo")), "C:\\foo"); EXPECT_EQ(fmt::format("{:g}", path("C:\\foo")), "C:/foo"); + EXPECT_EQ(fmt::format(L"{}", path(L"C:\\foo")), L"C:\\foo"); + EXPECT_EQ(fmt::format(L"{:g}", path(L"C:\\foo")), L"C:/foo"); EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448" L"\x0447\x044B\x043D\x0430")), From 0fe44d56d006d4750b41f4ebe056b9fe69902599 Mon Sep 17 00:00:00 2001 From: NgoQuocViet2001 Date: Wed, 28 Oct 2026 15:12:54 +0700 Subject: [PATCH 2/2] Keep generic path format when converting to wide strings on POSIX --- include/fmt/std.h | 13 ++++++------- test/std-test.cc | 7 +++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index a8f7c9f1..6ff7de63 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -102,21 +102,19 @@ template struct ubitint {}; #if FMT_CPP_LIB_FILESYSTEM template -auto get_path_string(const std::filesystem::path& p, - const std::basic_string& native) { +auto get_path_string(const std::basic_string& native) { if constexpr (std::is_same_v && std::is_same_v) { return to_utf8(native, to_utf8_error_policy::wtf); } else if constexpr (std::is_same_v) { return native; } else { - return p.string(); + return std::filesystem::path(native).string(); } } template void write_escaped_path(basic_memory_buffer& quoted, - const std::filesystem::path& p, const std::basic_string& native) { if constexpr (std::is_same_v && std::is_same_v) { @@ -128,7 +126,8 @@ void write_escaped_path(basic_memory_buffer& quoted, write_escaped_string( std::back_inserter(quoted), native); } else { - write_escaped_string(std::back_inserter(quoted), p.string()); + write_escaped_string(std::back_inserter(quoted), + std::filesystem::path(native).string()); } } @@ -339,11 +338,11 @@ template struct formatter { detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); if (!debug_) { - auto s = detail::get_path_string(p, path_string); + auto s = detail::get_path_string(path_string); return detail::write(ctx.out(), basic_string_view(s), specs); } auto quoted = basic_memory_buffer(); - detail::write_escaped_path(quoted, p, path_string); + detail::write_escaped_path(quoted, path_string); return detail::write(ctx.out(), basic_string_view(quoted.data(), quoted.size()), specs); diff --git a/test/std-test.cc b/test/std-test.cc index 32f77e55..7d992584 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -33,6 +33,13 @@ TEST(std_test, path) { EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\""); EXPECT_EQ(fmt::format("{:g}", path("/usr/bin")), "/usr/bin"); + + // libstdc++ collapses repeated separators in the generic format. + const path repeated = "a//b"; + EXPECT_EQ(fmt::format("{:g}", repeated), repeated.generic_string()); + EXPECT_EQ(fmt::format(L"{:g}", repeated), repeated.generic_wstring()); + EXPECT_EQ(fmt::format(L"{:?g}", repeated), + L"\"" + repeated.generic_wstring() + L"\""); # ifdef _WIN32 EXPECT_EQ(fmt::format("{}", path("C:\\foo")), "C:\\foo"); EXPECT_EQ(fmt::format("{:g}", path("C:\\foo")), "C:/foo");