Skip to content

Commit

Permalink
StringUtil: Add ToChars/fast_float FromChars
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek committed Jan 15, 2023
1 parent 81383af commit 01270ba
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/common/CMakeLists.txt
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/common/common.props
Expand Up @@ -4,7 +4,7 @@
<ClCompile>
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">WITH_OPENGL=1;WITH_VULKAN=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>

Expand Down
118 changes: 96 additions & 22 deletions src/common/string_util.h
Expand Up @@ -3,6 +3,7 @@

#pragma once
#include "types.h"
#include <charconv>
#include <cstdarg>
#include <cstddef>
#include <cstring>
Expand All @@ -12,13 +13,16 @@
#include <string_view>
#include <vector>

#if defined(__has_include) && __has_include(<charconv>)
#include <charconv>
#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 <locale>
#include <sstream>
#ifdef __APPLE__
#include <Availability.h>
#endif
#else
#include <sstream>
#endif

namespace StringUtil {
Expand Down Expand Up @@ -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<typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
inline std::optional<T> FromChars(const std::string_view& str, int base = 10)
{
T value;

#if defined(__has_include) && __has_include(<charconv>)
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<typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
inline std::optional<T> 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;
}
Expand All @@ -82,22 +97,75 @@ inline std::optional<T> FromChars(const std::string_view& str)
{
T value;

#if defined(__has_include) && __has_include(<charconv>) && 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 value;
}
template<typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
inline std::optional<T> 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;
#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;
}

/// Wrapper around std::to_chars
template<typename T, std::enable_if_t<std::is_integral<T>::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<typename T, std::enable_if_t<std::is_floating_point<T>::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<>
inline std::optional<bool> FromChars(const std::string_view& str, int base)
Expand All @@ -119,6 +187,12 @@ inline std::optional<bool> 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<std::vector<u8>> DecodeHex(const std::string_view& str);
std::string EncodeHex(const u8* data, int length);
Expand Down

0 comments on commit 01270ba

Please sign in to comment.