diff --git a/apps/openmw_test_suite/misc/test_stringops.cpp b/apps/openmw_test_suite/misc/test_stringops.cpp index 9df5b0114b..1cbbedc8b0 100644 --- a/apps/openmw_test_suite/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/misc/test_stringops.cpp @@ -182,4 +182,26 @@ namespace { EXPECT_FALSE(ciStartsWith("foo", "foo bar")); } + + TEST(MiscStringsFormat, string_format) + { + std::string f = "1%s2"; + EXPECT_EQ(Misc::StringUtils::format(f, ""), "12"); + } + + TEST(MiscStringsFormat, string_format_arg) + { + std::string arg = "12"; + EXPECT_EQ(Misc::StringUtils::format("1%s2", arg), "1122"); + } + + TEST(MiscStringsFormat, string_view_format_arg) + { + std::string f = "1%s2"; + std::string_view view = "12"; + EXPECT_EQ(Misc::StringUtils::format(f, view), "1122"); + EXPECT_EQ(Misc::StringUtils::format(f, view.substr(0, 1)), "112"); + EXPECT_EQ(Misc::StringUtils::format(f, view.substr(1, 1)), "122"); + EXPECT_EQ(Misc::StringUtils::format(f, view.substr(2)), "12"); + } } diff --git a/components/misc/strings/format.hpp b/components/misc/strings/format.hpp index 7b5de50fb4..af436b6229 100644 --- a/components/misc/strings/format.hpp +++ b/components/misc/strings/format.hpp @@ -11,55 +11,65 @@ namespace Misc::StringUtils { - struct Details + namespace Details { - std::vector mMemorySafety; - // Allow to convert complex arguments to C-style strings for format() function template T argument(T value) noexcept { + static_assert(!std::is_same_v, "std::string_view is not supported"); return value; } - template - T const * argument(std::basic_string_view const & value) noexcept - { - // TODO: switch to a format function that doesn't require null termination - auto& inserted = mMemorySafety.emplace_back(value); - return inserted.c_str(); - } - template T const * argument(std::basic_string const & value) noexcept { return value.c_str(); } - }; - // Requires some C++11 features: - // 1. std::string needs to be contiguous - // 2. std::snprintf with zero size (second argument) returns an output string size - // 3. variadic templates support + template + T nullTerminated(T value) noexcept + { + return value; + } + + template + std::basic_string nullTerminated(const std::basic_string_view& value) noexcept + { + // Ensure string_view arguments are null-terminated by creating a string + // TODO: Use a format function that doesn't require this workaround + return std::string{value}; + } + + // Requires some C++11 features: + // 1. std::string needs to be contiguous + // 2. std::snprintf with zero size (second argument) returns an output string size + // 3. variadic templates support + template + std::string format(const char* fmt, Args const & ... args) + { + const int size = std::snprintf(nullptr, 0, fmt, argument(args) ...); + if (size < 0) + throw std::runtime_error(std::string("Failed to compute resulting string size: ") + std::strerror(errno)); + // Note: sprintf also writes a trailing null character. We should remove it. + std::string ret(static_cast(size) + 1, '\0'); + if (std::sprintf(ret.data(), fmt, argument(args) ...) < 0) + throw std::runtime_error(std::string("Failed to format string: ") + std::strerror(errno)); + ret.erase(static_cast(size)); + return ret; + } + } + template std::string format(const char* fmt, Args const & ... args) { - Details details; - const int size = std::snprintf(nullptr, 0, fmt, details.argument(args) ...); - if (size < 0) - throw std::runtime_error(std::string("Failed to compute resulting string size: ") + std::strerror(errno)); - // Note: sprintf also writes a trailing null character. We should remove it. - std::string ret(static_cast(size) + 1, '\0'); - if (std::sprintf(ret.data(), fmt, details.argument(args) ...) < 0) - throw std::runtime_error(std::string("Failed to format string: ") + std::strerror(errno)); - ret.erase(static_cast(size)); - return ret; + return Details::format(fmt, Details::nullTerminated(args) ...); } template std::string format(const std::string& fmt, Args const & ... args) { - return format(fmt.c_str(), args ...); + return Details::format(fmt.c_str(), Details::nullTerminated(args) ...); } }