Skip to content

Commit 6ec7a6c

Browse files
committed
Add workaround for macOS not supporting std::from_chars<float>
1 parent 189602d commit 6ec7a6c

2 files changed

Lines changed: 32 additions & 16 deletions

File tree

SeQuant/core/utility/conversion.hpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <charconv>
77
#include <concepts>
8+
#include <sstream>
89
#include <string_view>
910
#include <system_error>
1011
#include <typeinfo>
@@ -55,7 +56,7 @@ T string_to_impl(std::string_view str, Arg &&arg) {
5556
} // namespace
5657

5758
template <typename T>
58-
concept string_to_supports =
59+
concept from_chars_supports =
5960
requires(const char *c, T &v) { std::from_chars(c, c + 1, v); };
6061

6162
/// Converts the provided string to the desired integral type.
@@ -74,7 +75,7 @@ concept string_to_supports =
7475
/// the parsed value can't be represented as a T.
7576
template <std::integral T>
7677
T string_to(std::string_view str, int base = 10) {
77-
static_assert(string_to_supports<T>,
78+
static_assert(from_chars_supports<T>,
7879
"Your C++ standard library is missing a std::from_chars "
7980
"implementation for this integral type");
8081
return string_to_impl<T>(str, base);
@@ -97,11 +98,28 @@ T string_to(std::string_view str, int base = 10) {
9798
template <std::floating_point T>
9899
T string_to(std::string_view str,
99100
std::chars_format fmt = std::chars_format::general) {
100-
static_assert(string_to_supports<T>,
101+
#ifndef __APPLE__
102+
static_assert(from_chars_supports<T>,
101103
"Your C++ standard library is missing a std::from_chars "
102104
"implementation for this floating point type");
103105

104106
return string_to_impl<T>(str, fmt);
107+
#else
108+
// For some reason it seems that Apple is unable to supply an implementation
109+
// of std::from_chars so we need to work around its (potential) absence
110+
if constexpr (from_chars_supports<T>) {
111+
// In case they update their standard lib…
112+
return string_to_impl<T>(str, fmt);
113+
}
114+
115+
// Workaround implementation that doesn't do any error checking - not great
116+
// but better than not being able to use this function at all
117+
std::stringstream stream{std::string(str)};
118+
T val = 0;
119+
stream >> val;
120+
121+
return val;
122+
#endif
105123
}
106124

107125
} // namespace sequant

tests/unit/test_utilities.cpp

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -630,20 +630,18 @@ TEST_CASE("utilities", "[utilities]") {
630630
"'4 ' could not be fully parsed as a")));
631631
}
632632
SECTION("float") {
633-
if constexpr (string_to_supports<float>) {
634-
REQUIRE_THAT(string_to<float>("42"),
635-
WithinAbs(42, std::numeric_limits<float>::epsilon()));
636-
REQUIRE_THAT(string_to<float>("3.14"),
637-
WithinAbs(3.14, std::numeric_limits<float>::epsilon()));
638-
REQUIRE_THAT(
639-
string_to<float>("-3.14159"),
640-
WithinAbs(-3.14159, std::numeric_limits<float>::epsilon()));
641-
if constexpr (string_to_supports<double>) {
642-
REQUIRE_THAT(string_to<double>("2.7182818284590"),
643-
WithinAbs(2.7182818284590,
644-
std::numeric_limits<double>::epsilon()));
645-
}
633+
REQUIRE_THAT(string_to<float>("42"),
634+
WithinAbs(42, std::numeric_limits<float>::epsilon()));
635+
REQUIRE_THAT(string_to<float>("3.14"),
636+
WithinAbs(3.14, std::numeric_limits<float>::epsilon()));
637+
REQUIRE_THAT(string_to<float>("-3.14159"),
638+
WithinAbs(-3.14159, std::numeric_limits<float>::epsilon()));
639+
REQUIRE_THAT(
640+
string_to<double>("2.7182818284590"),
641+
WithinAbs(2.7182818284590, std::numeric_limits<double>::epsilon()));
646642

643+
// Error handling is only available if std::from_chars is supported
644+
if constexpr (from_chars_supports<float>) {
647645
REQUIRE_THROWS_MATCHES(
648646
string_to<float>(" 3.14"), ConversionException,
649647
MessageMatches(ContainsSubstring("' 3.14' is not a valid")));

0 commit comments

Comments
 (0)