diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index bfa2950cb..44036ab25 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -61,7 +61,7 @@ add_library(common target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") -target_link_libraries(common PUBLIC fmt Threads::Threads vulkan-headers GSL) +target_link_libraries(common PUBLIC fmt Threads::Threads vulkan-headers GSL fast_float) target_link_libraries(common PRIVATE stb libchdr zlib minizip Zstd::Zstd "${CMAKE_DL_LIBS}") if(WIN32) diff --git a/src/common/common.props b/src/common/common.props index de4945fec..8c15b81fa 100644 --- a/src/common/common.props +++ b/src/common/common.props @@ -4,7 +4,7 @@ WITH_OPENGL=1;WITH_VULKAN=1;%(PreprocessorDefinitions) $(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan\include;%(AdditionalIncludeDirectories) - $(SolutionDir)dep\gsl\include;$(SolutionDir)dep\fmt\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\gsl\include;$(SolutionDir)dep\fast_float\include;$(SolutionDir)dep\fmt\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) diff --git a/src/common/string_util.h b/src/common/string_util.h index 18202eb3d..78432a83b 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -3,6 +3,7 @@ #pragma once #include "types.h" +#include #include #include #include @@ -12,13 +13,16 @@ #include #include -#if defined(__has_include) && __has_include() -#include -#ifndef _MSC_VER +#include "fast_float/fast_float.h" + +// Older versions of libstdc++ are missing support for from_chars() with floats, and was only recently +// merged in libc++. So, just fall back to stringstream (yuck!) on everywhere except MSVC. +#if !defined(_MSC_VER) +#include #include +#ifdef __APPLE__ +#include #endif -#else -#include #endif namespace StringUtil { @@ -56,23 +60,34 @@ static inline int Strncasecmp(const char* s1, const char* s2, std::size_t n) #endif } -/// Wrapper arond std::from_chars +/// Wrapper around std::from_chars template::value, bool> = true> inline std::optional FromChars(const std::string_view& str, int base = 10) { T value; -#if defined(__has_include) && __has_include() const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base); if (result.ec != std::errc()) return std::nullopt; -#else - std::string temp(str); - std::istringstream ss(temp); - ss >> std::setbase(base) >> value; - if (ss.fail()) + + return value; +} +template::value, bool> = true> +inline std::optional FromChars(const std::string_view& str, int base, std::string_view* endptr) +{ + T value; + + const char* ptr = str.data(); + const char* end = ptr + str.length(); + const std::from_chars_result result = std::from_chars(ptr, end, value, base); + if (result.ec != std::errc()) return std::nullopt; -#endif + + if (endptr) + { + const size_t remaining_len = end - ptr - 1; + *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); + } return value; } @@ -82,21 +97,74 @@ inline std::optional FromChars(const std::string_view& str) { T value; -#if defined(__has_include) && __has_include() && defined(_MSC_VER) - const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value); + const fast_float::from_chars_result result = fast_float::from_chars(str.data(), str.data() + str.length(), value); if (result.ec != std::errc()) return std::nullopt; -#else - /// libstdc++ does not support from_chars with floats yet - std::string temp(str); - std::istringstream ss(temp); - ss >> value; - if (ss.fail()) - return std::nullopt; -#endif return value; } +template::value, bool> = true> +inline std::optional FromChars(const std::string_view& str, std::string_view* endptr) +{ + T value; + + const char* ptr = str.data(); + const char* end = ptr + str.length(); + const fast_float::from_chars_result result = fast_float::from_chars(ptr, end, value); + if (result.ec != std::errc()) + return std::nullopt; + + if (endptr) + { + const size_t remaining_len = end - ptr - 1; + *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); + } + + return value; +} + +/// Wrapper around std::to_chars +template::value, bool> = true> +inline std::string ToChars(T value, int base = 10) +{ + // to_chars() requires macOS 10.15+. +#if !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 + constexpr size_t MAX_SIZE = 32; + char buf[MAX_SIZE]; + std::string ret; + + const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value, base); + if (result.ec == std::errc()) + ret.append(buf, result.ptr - buf); + + return ret; +#else + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss << std::setbase(base) << value; + return ss.str(); +#endif +} + +template::value, bool> = true> +inline std::string ToChars(T value) +{ + // No to_chars() in older versions of libstdc++/libc++. +#ifdef _MSC_VER + constexpr size_t MAX_SIZE = 64; + char buf[MAX_SIZE]; + std::string ret; + const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value); + if (result.ec == std::errc()) + ret.append(buf, result.ptr - buf); + return ret; +#else + std::ostringstream ss; + ss.imbue(std::locale::classic()); + ss << value; + return ss.str(); +#endif +} /// Explicit override for booleans template<> @@ -119,6 +187,12 @@ inline std::optional FromChars(const std::string_view& str, int base) return std::nullopt; } +template<> +inline std::string ToChars(bool value, int base) +{ + return std::string(value ? "true" : "false"); +} + /// Encode/decode hexadecimal byte buffers std::optional> DecodeHex(const std::string_view& str); std::string EncodeHex(const u8* data, int length);