#ifndef COMPONENTS_MISC_STRINGS_FORMAT_H #define COMPONENTS_MISC_STRINGS_FORMAT_H #include #include #include #include #include #include #include #include #include namespace Misc::StringUtils { namespace Details { // 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"); static_assert(!std::is_same_v, "MyGUI::UString is not supported"); return value; } template T const* argument(std::basic_string const& value) noexcept { return value.c_str(); } 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::system_error(errno, std::generic_category(), "Failed to compute resulting string size"); // 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::system_error(errno, std::generic_category(), "Failed to format string"); ret.erase(static_cast(size)); return ret; } } template std::string format(const char* fmt, Args const&... args) { return Details::format(fmt, Details::nullTerminated(args)...); } template std::string format(const std::string& fmt, Args const&... args) { return Details::format(fmt.c_str(), Details::nullTerminated(args)...); } } #endif