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);