mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-16 21:25:49 +00:00
use to/from_chars in attribute_value and lexical_cast (#8790)
This makes attribute_value and lexical_cast use the "new" to/from_chars api. Its main advantages are: - It's guaranteed to be locale independent, hopefully fixing all cases of #3945 and similar - It fixes some cases config serialization, in particular the test ``` cfg["x"] = "9.87654321"; BOOST_CHECK_EQUAL(cfg["x"], 9.87654321); ``` - Previously the lexical_cast implementation used exception handling for invalid formats (catching std::invalid_argument) which made noise during debugging (and is also slower if it is not optimized out). - It's faster So far afaik the only compiler which has a complete and proper to/from_chars implementation is msvc, gccs implementation of from_chars sometimes uses strtod under the hood and clang simply hasn't implemented from_chars for floating point numbers yet at all (actually the upcomig clang 20 will have it). Luckily for us, there is now also boost::charconv that can be used. So this raises to minimum build requirement to have at least one of: - msvc 2019 update 5 - gcc 11 - clang 14 (i have added a fallback implementation of from_chars for this case, that doesn't support all of its features, and is probably certainly not as fast as the boost version, but supports the features that we use from it) - boost 1.85 Since in particular the gcc implementation isn't that good (at least it on gcc11), boost charconv is the preferred implementation that is used if available. This also removes a strange overload for pointers to integers in lexical_cast while changing lexical_cast to use the new api.
This commit is contained in:
parent
2139d5b7b6
commit
5801b5bcdb
|
@ -385,6 +385,7 @@ if env["prereqs"]:
|
|||
conf.CheckBoost("program_options", require_version = boost_version) & \
|
||||
conf.CheckBoost("random", require_version = boost_version) & \
|
||||
conf.CheckBoost("smart_ptr", header_only = True) & \
|
||||
conf.CheckBoostCharconv() & \
|
||||
CheckAsio(conf) & \
|
||||
conf.CheckBoost("thread") & \
|
||||
conf.CheckBoost("locale") & \
|
||||
|
|
|
@ -171,20 +171,20 @@ test_lexical_cast_throw<unsigned long long>
|
|||
test_lexical_cast_throw<float>
|
||||
test_lexical_cast_throw<double>
|
||||
test_lexical_cast_throw<long double>
|
||||
test_lexical_cast_signed<signed char>
|
||||
test_lexical_cast_signed<short>
|
||||
test_lexical_cast_signed<int>
|
||||
test_lexical_cast_signed<long>
|
||||
test_lexical_cast_long_long
|
||||
test_lexical_cast_unsigned<unsigned char>
|
||||
test_lexical_cast_unsigned<unsigned short>
|
||||
test_lexical_cast_unsigned<unsigned int>
|
||||
test_lexical_cast_unsigned<unsigned long>
|
||||
test_lexical_cast_unsigned_long_long
|
||||
test_lexical_arethmetic_signed<signed char>
|
||||
test_lexical_arethmetic_signed<short>
|
||||
test_lexical_arethmetic_signed<int>
|
||||
test_lexical_arethmetic_signed<long>
|
||||
test_lexical_arethmetic_signed<long long>
|
||||
test_lexical_arethmetic_signed<unsigned char>
|
||||
test_lexical_arethmetic_signed<unsigned short>
|
||||
test_lexical_arethmetic_signed<unsigned int>
|
||||
test_lexical_arethmetic_signed<unsigned long>
|
||||
test_lexical_arethmetic_signed<unsigned long long>
|
||||
test_lexical_arethmetic_signed<float>
|
||||
test_lexical_arethmetic_signed<double>
|
||||
test_lexical_arethmetic_signed<long double>
|
||||
test_lexical_cast_bool
|
||||
test_lexical_cast_floating_point<float>
|
||||
test_lexical_cast_floating_point<double>
|
||||
test_lexical_cast_floating_point<long double>
|
||||
test_lexical_cast_result
|
||||
test_map_location/map_location_characterization_test_radial_mode
|
||||
test_map_location/reality_check_vector_negation
|
||||
|
|
|
@ -1247,6 +1247,8 @@
|
|||
<Unit filename="../../src/units/unit_alignments.hpp" />
|
||||
<Unit filename="../../src/utils/any.hpp" />
|
||||
<Unit filename="../../src/utils/back_edge_detector.hpp" />
|
||||
<Unit filename="../../src/utils/charconv.cpp" />
|
||||
<Unit filename="../../src/utils/charconv.hpp" />
|
||||
<Unit filename="../../src/utils/config_filters.cpp" />
|
||||
<Unit filename="../../src/utils/config_filters.hpp" />
|
||||
<Unit filename="../../src/utils/const_clone.hpp" />
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
1BC74FED857215A162E9E0F2 /* tab_container.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 162C4B1E9F7373592D0F3B89 /* tab_container.cpp */; };
|
||||
1C3D48879EAC414AE3DB122E /* combobox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 875E45698F8A8D5B750E7317 /* combobox.cpp */; };
|
||||
285C4E7A9E891E1DCB215683 /* back_edge_detector.hpp in Headers */ = {isa = PBXBuildFile; fileRef = DA034C90BB2E6C060B0A0B93 /* back_edge_detector.hpp */; };
|
||||
362245818DDA4C7E4CC8165A /* charconv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CDC443798CB3254283483D0 /* charconv.cpp */; };
|
||||
365D4F89BD511BC074E639D7 /* migrate_version_selection.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B3DE4F95AF72C6F6BC37E695 /* migrate_version_selection.hpp */; };
|
||||
36B146FAA79A55E9F43723B1 /* general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84234C54BB84519421FD4136 /* general.cpp */; };
|
||||
36D74F7F8D7655ACCABE562D /* edit_pbl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6FA542D78393E8FF067775DA /* edit_pbl.cpp */; };
|
||||
|
@ -1110,6 +1111,7 @@
|
|||
91FAC70A1C7FBC3400DAB2C3 /* lua_formula_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */; };
|
||||
91FBBAD81CB6BC3F00470BFE /* filesystem_sdl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FBBAD71CB6BC3F00470BFE /* filesystem_sdl.cpp */; };
|
||||
91FBBADB1CB6D1B700470BFE /* markov_generator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FBBAD91CB6D1B700470BFE /* markov_generator.cpp */; };
|
||||
92644A76AAB2F29A77107DC0 /* charconv.hpp in Headers */ = {isa = PBXBuildFile; fileRef = A03A423AB502FE015C91F1C9 /* charconv.hpp */; };
|
||||
9577DD192C1E0AD20031135F /* ai_target.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 9577DD182C1E0AD20031135F /* ai_target.hpp */; };
|
||||
9577DD1B2C1E0AF40031135F /* campaign_type.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 9577DD1A2C1E0AF40031135F /* campaign_type.hpp */; };
|
||||
9577DD1D2C1E0C020031135F /* defeat_condition.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 9577DD1C2C1E0C020031135F /* defeat_condition.hpp */; };
|
||||
|
@ -1142,6 +1144,7 @@
|
|||
95EB8A58287B138700B09F95 /* draw_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95EB8A50287A02B800B09F95 /* draw_manager.cpp */; };
|
||||
95EB8A59287B139800B09F95 /* top_level_drawable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95EB8A54287A02EC00B09F95 /* top_level_drawable.cpp */; };
|
||||
97714C7A9FF444E29DCEF0BA /* carryover_show_gold.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09A440B1A671C45BE2924FB4 /* carryover_show_gold.cpp */; };
|
||||
99494BD0ABBAE79FB3814E00 /* charconv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CDC443798CB3254283483D0 /* charconv.cpp */; };
|
||||
9B4B41D29C90B05F03DE21B0 /* edit_pbl_translation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = C679447D91FD3623CC852FF8 /* edit_pbl_translation.hpp */; };
|
||||
9C2743DE8100448B66F7E0AF /* combobox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 875E45698F8A8D5B750E7317 /* combobox.cpp */; };
|
||||
9C6342BC8A95B6D23D384486 /* gui_test_dialog.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */; };
|
||||
|
@ -1477,6 +1480,7 @@
|
|||
F4E4E0B11367241E001C7528 /* suppose_dead.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4E4E0AF1367241E001C7528 /* suppose_dead.cpp */; };
|
||||
F4E4E0B41367244F001C7528 /* image_modifications.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4E4E0B21367244F001C7528 /* image_modifications.cpp */; };
|
||||
F6E34C7CBC418F214B43BC39 /* general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84234C54BB84519421FD4136 /* general.cpp */; };
|
||||
F7524948ADF9BC097E6D8DBC /* charconv.hpp in Headers */ = {isa = PBXBuildFile; fileRef = A03A423AB502FE015C91F1C9 /* charconv.hpp */; };
|
||||
F8974F919B014C9E7103A6B9 /* markup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B5EE4DE4B73C204AC0666900 /* markup.cpp */; };
|
||||
F96A40219A964A8022910D73 /* back_edge_detector.hpp in Headers */ = {isa = PBXBuildFile; fileRef = DA034C90BB2E6C060B0A0B93 /* back_edge_detector.hpp */; };
|
||||
FC66414CB2E3F0E8B0B747B6 /* tod_new_schedule.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5D46466DBCD81B13621C7342 /* tod_new_schedule.hpp */; };
|
||||
|
@ -1598,6 +1602,7 @@
|
|||
0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gui_test_dialog.hpp; sourceTree = "<group>"; };
|
||||
09A440B1A671C45BE2924FB4 /* carryover_show_gold.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = carryover_show_gold.cpp; sourceTree = "<group>"; };
|
||||
0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = multiline_text.cpp; sourceTree = "<group>"; };
|
||||
0CDC443798CB3254283483D0 /* charconv.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = charconv.cpp; path = charconv.cpp; sourceTree = "<group>"; };
|
||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
1234567890ABCDEF12345680 /* file_progress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_progress.cpp; sourceTree = "<group>"; };
|
||||
1234567890ABCDEF12345681 /* file_progress.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = file_progress.hpp; sourceTree = "<group>"; };
|
||||
|
@ -2448,6 +2453,7 @@
|
|||
95EB8A51287A02B800B09F95 /* draw_manager.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = draw_manager.hpp; sourceTree = "<group>"; };
|
||||
95EB8A53287A02EC00B09F95 /* top_level_drawable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = top_level_drawable.hpp; path = gui/core/top_level_drawable.hpp; sourceTree = "<group>"; };
|
||||
95EB8A54287A02EC00B09F95 /* top_level_drawable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = top_level_drawable.cpp; path = gui/core/top_level_drawable.cpp; sourceTree = "<group>"; };
|
||||
A03A423AB502FE015C91F1C9 /* charconv.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = charconv.hpp; path = charconv.hpp; sourceTree = "<group>"; };
|
||||
A05D48F0A2C022FC128C8B3E /* edit_unit.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = edit_unit.cpp; sourceTree = "<group>"; };
|
||||
B2CC45FEA71445AE817CAA6B /* edit_pbl.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = edit_pbl.hpp; sourceTree = "<group>"; };
|
||||
B3534BCB9BB2673B5E513D67 /* combobox.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = combobox.hpp; sourceTree = "<group>"; };
|
||||
|
@ -3104,6 +3110,7 @@
|
|||
468A5BAC258CD3EB004A80EF /* libboost_program_options-mt.dylib in Frameworks */,
|
||||
46515C372569CE0B00084CE2 /* libcrypto.1.1.dylib in Frameworks */,
|
||||
468A5BA6258CD3D8004A80EF /* libboost_iostreams-mt.dylib in Frameworks */,
|
||||
462311AA2D047BF400DAE465 /* libboost_charconv-mt.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -3167,6 +3174,7 @@
|
|||
46515C352569CE0B00084CE2 /* libcrypto.1.1.dylib in Frameworks */,
|
||||
468A5BA7258CD3D9004A80EF /* libboost_iostreams-mt.dylib in Frameworks */,
|
||||
46EA7A4B258FDC6100043333 /* libboost_context-mt.dylib in Frameworks */,
|
||||
462311AA2D047BF400DAE465 /* libboost_charconv-mt.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -4543,6 +4551,8 @@
|
|||
9577DD342C1E1E1C0031135F /* wesnoth_epoch.hpp */,
|
||||
492144E3AE22C0C7A2D5F27C /* spritesheet_generator.cpp */,
|
||||
3D284B9A81882806D8B25006 /* spritesheet_generator.hpp */,
|
||||
0CDC443798CB3254283483D0 /* charconv.cpp */,
|
||||
A03A423AB502FE015C91F1C9 /* charconv.hpp */,
|
||||
);
|
||||
path = utils;
|
||||
sourceTree = "<group>";
|
||||
|
@ -5226,6 +5236,7 @@
|
|||
9577DD402C1E36170031135F /* carryover_show_gold.hpp in Headers */,
|
||||
80724CBB88A3B6C36D1E3199 /* spritesheet_generator.hpp in Headers */,
|
||||
0DA840E1AD033775DD626F42 /* markup.hpp in Headers */,
|
||||
F7524948ADF9BC097E6D8DBC /* charconv.hpp in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -5247,6 +5258,7 @@
|
|||
6A1F44688986D07EB5DBEF75 /* gui_test_dialog.hpp in Headers */,
|
||||
3C0F4FFA9A0331ED6846D216 /* spritesheet_generator.hpp in Headers */,
|
||||
B7344A69B44FC3CFB1DE78BB /* markup.hpp in Headers */,
|
||||
92644A76AAB2F29A77107DC0 /* charconv.hpp in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -5986,6 +5998,7 @@
|
|||
9FE64884AE8121CDBABF7D8A /* preferences.cpp in Sources */,
|
||||
59F24113ACB3713647622A17 /* spritesheet_generator.cpp in Sources */,
|
||||
F8974F919B014C9E7103A6B9 /* markup.cpp in Sources */,
|
||||
99494BD0ABBAE79FB3814E00 /* charconv.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -6665,6 +6678,7 @@
|
|||
C3854DF5A850564161932EE5 /* test_help_markup.cpp in Sources */,
|
||||
4A1D4916A16C7C6E07D0BAB2 /* spritesheet_generator.cpp in Sources */,
|
||||
DA6F4A0B9083938EDB32E7C9 /* markup.cpp in Sources */,
|
||||
362245818DDA4C7E4CC8165A /* charconv.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ from os.path import join, dirname, basename, normpath
|
|||
import sys
|
||||
from glob import glob
|
||||
import re
|
||||
from SCons.Script import *
|
||||
|
||||
def find_boost(env):
|
||||
prefixes = [env["prefix"], "C:\\Boost"]
|
||||
|
@ -69,6 +70,7 @@ def CheckBoost(context, boost_lib, require_version = None, header_only = False):
|
|||
"random" : "random/random_number_generator.hpp",
|
||||
"system" : "system/error_code.hpp",
|
||||
"context" : "context/continuation.hpp",
|
||||
"charconv" : "charconv.hpp",
|
||||
"coroutine" : "coroutine/coroutine.hpp",
|
||||
"graph" : "graph/graph_traits.hpp" }
|
||||
|
||||
|
@ -233,4 +235,48 @@ def CheckBoostLocaleBackends(context, backends):
|
|||
|
||||
return False
|
||||
|
||||
config_checks = { "CheckBoost" : CheckBoost, "CheckBoostIostreamsGZip" : CheckBoostIostreamsGZip, "CheckBoostIostreamsBZip2" : CheckBoostIostreamsBZip2, "CheckBoostLocaleBackends" : CheckBoostLocaleBackends }
|
||||
|
||||
def CheckBoostCharconv(context):
|
||||
|
||||
test_program_std = """
|
||||
#include <charconv>
|
||||
#include <array>
|
||||
|
||||
int main()
|
||||
{
|
||||
double num = 6.6;
|
||||
std::array<char, 50> buffer;
|
||||
std::to_chars(buffer.data(), buffer.data() + buffer.size(), num);
|
||||
return 0;
|
||||
}
|
||||
\n"""
|
||||
|
||||
test_program_quadmath = """
|
||||
#include <quadmath.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
__float128 f = -2.0Q;
|
||||
f = fabsq(f);
|
||||
|
||||
return 0;
|
||||
}"""
|
||||
|
||||
has_boost_charconv = CheckBoost(context, "charconv")
|
||||
if has_boost_charconv:
|
||||
# we dont really use quadmath, but it seems like boost
|
||||
# creates a dependency on it anyways if quadmath is available.
|
||||
if context.TryCompile(test_program_quadmath, ".c"):
|
||||
context.env.PrependUnique(LIBS = ["quadmath"])
|
||||
return True
|
||||
|
||||
else:
|
||||
# boost charconv is better than std::charconv on most compilers
|
||||
# so only look for std::charconv if boost charconv is not available.
|
||||
context.Message("Checking std::charconv ... ")
|
||||
|
||||
has_std_charconv = context.TryLink(test_program_std, ".cpp")
|
||||
context.Result(has_std_charconv)
|
||||
return has_std_charconv
|
||||
|
||||
config_checks = { "CheckBoost" : CheckBoost, "CheckBoostCharconv" : CheckBoostCharconv, "CheckBoostIostreamsGZip" : CheckBoostIostreamsGZip, "CheckBoostIostreamsBZip2" : CheckBoostIostreamsBZip2, "CheckBoostLocaleBackends" : CheckBoostLocaleBackends }
|
||||
|
|
|
@ -388,6 +388,7 @@ units/race.cpp
|
|||
units/types.cpp
|
||||
units/udisplay.cpp
|
||||
units/unit.cpp
|
||||
utils/charconv.cpp
|
||||
utils/config_filters.cpp
|
||||
utils/context_free_grammar_generator.cpp
|
||||
utils/irdya_datetime.cpp
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
#include "lexical_cast.hpp"
|
||||
#include "log.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
#include "utils/charconv.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
|
||||
static lg::log_domain log_config("config");
|
||||
|
@ -113,19 +115,30 @@ namespace
|
|||
* @returns true if the conversion was successful and the source string
|
||||
* can be reobtained by streaming the result.
|
||||
*/
|
||||
|
||||
template<typename TNum>
|
||||
bool str_equals_number(std::string_view str, TNum num)
|
||||
{
|
||||
return utils::charconv_buffer<TNum>(num).get_view() == str;
|
||||
}
|
||||
|
||||
template<typename To>
|
||||
bool from_string_verify(const std::string& source, To& res)
|
||||
bool from_string_verify(std::string_view source, To& res)
|
||||
{
|
||||
// Check 1: convertible to the target type.
|
||||
std::istringstream in_str(source);
|
||||
if(!(in_str >> res)) {
|
||||
auto [ptr, ec] = utils::charconv::from_chars(source.data(), source.data() + source.size(), res);
|
||||
if(ec != std::errc()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ptr != source.data() + source.size()) {
|
||||
// We didn't use some characters, its impossible that "Check 2" gives the same string back.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check 2: convertible back to the same string.
|
||||
std::ostringstream out_str;
|
||||
out_str << res;
|
||||
return out_str.str() == source;
|
||||
return str_equals_number(source, res);
|
||||
}
|
||||
} // end anon namespace
|
||||
|
||||
|
@ -158,9 +171,9 @@ config_attribute_value& config_attribute_value::operator=(std::string&& v)
|
|||
}
|
||||
|
||||
// Attempt to convert to a number.
|
||||
char* eptr;
|
||||
double d = strtod(v.c_str(), &eptr);
|
||||
if(*eptr == '\0') {
|
||||
double d = 0;
|
||||
auto [eptr, ec] = utils::charconv::from_chars(v.data(), v.data() + v.size(), d);
|
||||
if(eptr == v.data() + v.size() && ec == std::errc()) {
|
||||
// Possibly a number. See what type it should be stored in.
|
||||
// (All conversions will be from the string since the largest integer
|
||||
// type could have more precision than a double.)
|
||||
|
@ -181,9 +194,7 @@ config_attribute_value& config_attribute_value::operator=(std::string&& v)
|
|||
// This does not look like an integer, so it should be a double.
|
||||
// However, make sure it can convert back to the same string (in
|
||||
// case this is a string that just looks like a numeric value).
|
||||
std::ostringstream tester;
|
||||
tester << d;
|
||||
if(tester.str() == v) {
|
||||
if(str_equals_number(v, d)) {
|
||||
value_ = d;
|
||||
return *this;
|
||||
}
|
||||
|
@ -311,9 +322,10 @@ public:
|
|||
std::string operator()(const utils::monostate &) const { return default_; }
|
||||
std::string operator()(const yes_no & b) const { return b.str(); }
|
||||
std::string operator()(const true_false & b) const { return b.str(); }
|
||||
std::string operator()(int i) const { return std::to_string(i); }
|
||||
std::string operator()(unsigned long long u) const { return std::to_string(u); }
|
||||
std::string operator()(double d) const { return lexical_cast<std::string>(d); }
|
||||
//this has to use the same method as in from_string_verify
|
||||
std::string operator()(int i) const { return utils::charconv_buffer(i).to_string(); }
|
||||
std::string operator()(unsigned long long u) const { return utils::charconv_buffer(u).to_string(); }
|
||||
std::string operator()(double d) const { return utils::charconv_buffer(d).to_string(); }
|
||||
std::string operator()(const std::string& s) const { return s; }
|
||||
std::string operator()(const t_string& s) const { return s.str(); }
|
||||
};
|
||||
|
|
|
@ -42,6 +42,8 @@
|
|||
#define DEBUG_THROW(id) throw id;
|
||||
#else
|
||||
|
||||
#include "utils/charconv.hpp"
|
||||
|
||||
#include "utils/optional_fwd.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
|
@ -163,396 +165,129 @@ struct lexical_caster<
|
|||
std::string
|
||||
, From
|
||||
, void
|
||||
, std::enable_if_t<std::is_integral_v<std::remove_pointer_t<From>>>
|
||||
, std::enable_if_t<std::is_arithmetic_v<From>>
|
||||
>
|
||||
{
|
||||
std::string operator()(From value, utils::optional<std::string>) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To std::string - From integral (pointer)");
|
||||
|
||||
std::stringstream sstr;
|
||||
sstr << value;
|
||||
return sstr.str();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a long long from a (const) char*.
|
||||
* @note is separate from the other signed types since a long long has a
|
||||
* performance penalty at 32 bit systems.
|
||||
*/
|
||||
template <class From>
|
||||
struct lexical_caster<
|
||||
long long
|
||||
, From
|
||||
, void
|
||||
, std::enable_if_t<std::is_same_v<From, const char*> || std::is_same_v<From, char*>>
|
||||
>
|
||||
{
|
||||
long long operator()(From value, utils::optional<long long> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To long long - From (const) char*");
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<long long>(std::string(value), *fallback);
|
||||
DEBUG_THROW("specialized - To std::string - From arithmetic");
|
||||
if constexpr (std::is_same_v<bool, From>) {
|
||||
return value ? "1" : "0";
|
||||
} else {
|
||||
return lexical_cast<long long>(std::string(value));
|
||||
return utils::charconv_buffer(value).to_string();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a long long from a std::string.
|
||||
* @note is separate from the other signed types since a long long has a
|
||||
* performance penalty at 32 bit systems.
|
||||
*/
|
||||
template <>
|
||||
struct lexical_caster<
|
||||
long long
|
||||
, std::string
|
||||
>
|
||||
{
|
||||
long long operator()(const std::string& value, utils::optional<long long> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To long long - From std::string");
|
||||
|
||||
if(value.empty()) {
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stoll(value);
|
||||
} catch(const std::invalid_argument&) {
|
||||
} catch(const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a signed type from a (const) char*.
|
||||
*/
|
||||
template <class To, class From>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, From
|
||||
, std::enable_if_t<std::is_integral_v<To> && std::is_signed_v<To> && !std::is_same_v<To, long long>>
|
||||
, std::enable_if_t<std::is_same_v<From, const char*> || std::is_same_v<From, char*>>
|
||||
>
|
||||
{
|
||||
To operator()(From value, utils::optional<To> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To signed - From (const) char*");
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string(value), *fallback);
|
||||
} else {
|
||||
return lexical_cast<To>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a signed type from a std::string.
|
||||
*/
|
||||
template <class To>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, std::string
|
||||
, std::enable_if_t<std::is_integral_v<To> && std::is_signed_v<To> && !std::is_same_v<To, long long>>
|
||||
>
|
||||
{
|
||||
To operator()(const std::string& value, utils::optional<To> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To signed - From std::string");
|
||||
|
||||
if(value.empty()) {
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
long res = std::stol(value);
|
||||
if(std::numeric_limits<To>::lowest() <= res && std::numeric_limits<To>::max() >= res) {
|
||||
return static_cast<To>(res);
|
||||
}
|
||||
} catch(const std::invalid_argument&) {
|
||||
} catch(const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a floating point type from a (const) char*.
|
||||
*/
|
||||
template <class To, class From>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, From
|
||||
, std::enable_if_t<std::is_floating_point_v<To>>
|
||||
, std::enable_if_t<std::is_same_v<From, const char*> || std::is_same_v<From, char*>>
|
||||
>
|
||||
{
|
||||
To operator()(From value, utils::optional<To> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To floating point - From (const) char*");
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string(value), *fallback);
|
||||
} else {
|
||||
return lexical_cast<To>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a floating point type from a std::string.
|
||||
*/
|
||||
template <class To>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, std::string
|
||||
, std::enable_if_t<std::is_floating_point_v<To>>
|
||||
>
|
||||
{
|
||||
To operator()(const std::string& value, utils::optional<To> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To floating point - From std::string");
|
||||
|
||||
if(value.empty()) {
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly reject hexadecimal values. Unit tests of the config class require that.
|
||||
if(value.find_first_of("Xx") != std::string::npos) {
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
long double res = std::stold(value);
|
||||
if((static_cast<long double>(std::numeric_limits<To>::lowest()) <= res) && (static_cast<long double>(std::numeric_limits<To>::max()) >= res)) {
|
||||
return static_cast<To>(res);
|
||||
}
|
||||
} catch(const std::invalid_argument&) {
|
||||
} catch(const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a unsigned long long from a (const) char*.
|
||||
* @note is separate from the other unsigned types since a unsigned long long
|
||||
* has a performance penalty at 32 bit systems.
|
||||
*/
|
||||
template <class From>
|
||||
struct lexical_caster<
|
||||
unsigned long long
|
||||
, From
|
||||
, void
|
||||
, std::enable_if_t<std::is_same_v<From, const char*> || std::is_same_v<From, char*>>
|
||||
>
|
||||
{
|
||||
unsigned long long operator()(From value, utils::optional<unsigned long long> fallback) const
|
||||
{
|
||||
DEBUG_THROW(
|
||||
"specialized - To unsigned long long - From (const) char*");
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<unsigned long long>(std::string(value), *fallback);
|
||||
} else {
|
||||
return lexical_cast<unsigned long long>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a unsigned long long from a std::string.
|
||||
* @note is separate from the other unsigned types since a unsigned long long
|
||||
* has a performance penalty at 32 bit systems.
|
||||
*/
|
||||
template <>
|
||||
struct lexical_caster<
|
||||
unsigned long long
|
||||
, std::string
|
||||
>
|
||||
{
|
||||
unsigned long long operator()(const std::string& value, utils::optional<unsigned long long> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To unsigned long long - From std::string");
|
||||
|
||||
if(value.empty()) {
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stoull(value);
|
||||
} catch(const std::invalid_argument&) {
|
||||
} catch(const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a unsigned type from a (const) char*.
|
||||
*/
|
||||
template <class To, class From>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, From
|
||||
, std::enable_if_t<std::is_unsigned_v<To> && !std::is_same_v<To, unsigned long long>>
|
||||
, std::enable_if_t<std::is_same_v<From, const char*> || std::is_same_v<From, char*>>
|
||||
>
|
||||
{
|
||||
To operator()(From value, utils::optional<To> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To unsigned - From (const) char*");
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string(value), *fallback);
|
||||
} else {
|
||||
return lexical_cast<To>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a unsigned type from a std::string.
|
||||
*/
|
||||
template <class To>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, std::string
|
||||
, std::enable_if_t<std::is_unsigned_v<To>>
|
||||
>
|
||||
{
|
||||
To operator()(const std::string& value, utils::optional<To> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To unsigned - From std::string");
|
||||
|
||||
if(value.empty()) {
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
unsigned long res = std::stoul(value);
|
||||
// No need to check the lower bound, it's zero for all unsigned types.
|
||||
if(std::numeric_limits<To>::max() >= res) {
|
||||
return static_cast<To>(res);
|
||||
}
|
||||
} catch(const std::invalid_argument&) {
|
||||
} catch(const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a bool from a std::string.
|
||||
* @note is specialized to silence C4804 from MSVC.
|
||||
*/
|
||||
template <>
|
||||
struct lexical_caster<bool, std::string>
|
||||
{
|
||||
bool operator()(const std::string& value, utils::optional<bool>) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To bool - From std::string");
|
||||
|
||||
return value == "1";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a bool from a (const) char*.
|
||||
* @note is specialized to silence C4804 from MSVC.
|
||||
*/
|
||||
template <class From>
|
||||
struct lexical_caster<
|
||||
bool
|
||||
, From
|
||||
, std::string_view
|
||||
, void
|
||||
, std::enable_if_t<std::is_same_v<From, const char*> || std::is_same_v<From, char*>>
|
||||
>
|
||||
, void
|
||||
>
|
||||
{
|
||||
bool operator()(From value, utils::optional<bool>) const
|
||||
bool operator()(std::string_view str, utils::optional<bool> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To bool - From (const) char*");
|
||||
DEBUG_THROW("specialized - To bool - From string");
|
||||
utils::trim_for_from_chars(str);
|
||||
if(str == "1") {
|
||||
return true;
|
||||
} else if(str == "0") {
|
||||
return false;
|
||||
} else if (fallback) {
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return lexical_cast<bool>(std::string(value));
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning arithmetic from a string_view, also used by std::string and (const) char*
|
||||
*/
|
||||
template <typename To>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, std::string_view
|
||||
, std::enable_if_t<std::is_arithmetic_v<To>>
|
||||
, void
|
||||
>
|
||||
{
|
||||
To operator()(std::string_view str, utils::optional<To> fallback) const
|
||||
{
|
||||
DEBUG_THROW("specialized - To arithmetic - From string");
|
||||
|
||||
To res = To();
|
||||
|
||||
utils::trim_for_from_chars(str);
|
||||
|
||||
auto [ptr, ec] = utils::charconv::from_chars(str.data(), str.data() + str.size(), res);
|
||||
if(ec == std::errc()) {
|
||||
return res;
|
||||
} else if(fallback){
|
||||
return *fallback;
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning arithmetic from a std::string
|
||||
*/
|
||||
template <typename To>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, std::string
|
||||
, std::enable_if_t<std::is_arithmetic_v<To>>
|
||||
, void
|
||||
>
|
||||
{
|
||||
To operator()(const std::string& value, utils::optional<To> fallback) const
|
||||
{
|
||||
// Dont DEBUG_THROW. the test shodul test what actual implementaiton is used in the end, not which specialazion that jsut forwards to another
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string_view(value), *fallback);
|
||||
} else {
|
||||
return lexical_cast<To>(std::string_view(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning arithmetic from a (const) char*.
|
||||
*/
|
||||
template <class To, class From>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, From
|
||||
, std::enable_if_t<std::is_arithmetic_v<To> >
|
||||
, std::enable_if_t<std::is_same_v<From, const char*> || std::is_same_v<From, char*> >
|
||||
>
|
||||
{
|
||||
To operator()(const std::string& value, utils::optional<To> fallback) const
|
||||
{
|
||||
// Dont DEBUG_THROW. the test shodul test what actual implementaiton is used in the end, not which specialazion that jsut forwards to another
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string_view(value), *fallback);
|
||||
} else {
|
||||
return lexical_cast<To>(std::string_view(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "serialization/preprocessor.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
#include "serialization/validator.hpp"
|
||||
#include "utils/charconv.hpp"
|
||||
#include "wesconfig.h"
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
@ -537,7 +538,13 @@ public:
|
|||
void operator()(const T& v) const
|
||||
{
|
||||
indent();
|
||||
out_ << key_ << '=' << v << '\n';
|
||||
if constexpr(std::is_arithmetic_v<T>) {
|
||||
// for number values, this has to use the same method as in from_string_verify
|
||||
auto buf = utils::charconv_buffer(v);
|
||||
out_ << key_ << '=' << buf.get_view() << '\n';
|
||||
} else {
|
||||
out_ << key_ << '=' << v << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -231,7 +231,7 @@ BOOST_AUTO_TEST_CASE(test_config_attribute_value)
|
|||
BOOST_CHECK_EQUAL(c["x"], "no");
|
||||
c["x"] = 1.23456789;
|
||||
BOOST_CHECK_EQUAL(c["x"], 1.23456789);
|
||||
#if 0 // FIXME: this should work. it doesn't work. looks like it's getting stored as 9.8765432099999995
|
||||
#if 1 // FIXME: this should work. it doesn't work. looks like it's getting stored as 9.8765432099999995
|
||||
c["x"] = "9.87654321";
|
||||
BOOST_CHECK_EQUAL(c["x"], 9.87654321);
|
||||
#endif
|
||||
|
|
|
@ -36,11 +36,11 @@ namespace test_throw {
|
|||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
using test_match_types = std::tuple<
|
||||
using test_bool_types = std::tuple<
|
||||
/* note Wesnoth's coding style doesn't allow w_char so ignore them. */
|
||||
bool>;
|
||||
|
||||
bool,
|
||||
|
||||
using test_integral_types = std::tuple<
|
||||
/*
|
||||
* We don't want chars to match since a string cast of a char is
|
||||
* ambiguous; does the user want it interpreted as a char or as a number?
|
||||
|
@ -51,10 +51,16 @@ using test_match_types = std::tuple<
|
|||
unsigned short, unsigned int, unsigned long, unsigned long long
|
||||
>;
|
||||
|
||||
using test_nomatch_types = std::tuple<float, double, long double>;
|
||||
using test_floating_point_types = std::tuple<float, double, long double>;
|
||||
|
||||
|
||||
|
||||
using test_match_types = decltype(std::tuple_cat(test_bool_types{}, test_integral_types{}));
|
||||
using test_nomatch_types = decltype(std::tuple_cat(test_floating_point_types{}));
|
||||
using test_types = decltype(std::tuple_cat(test_nomatch_types{}, test_match_types{}));
|
||||
|
||||
using test_arethmetic_types = decltype(std::tuple_cat(test_integral_types{}, test_floating_point_types{}));
|
||||
|
||||
namespace {
|
||||
|
||||
std::string result;
|
||||
|
@ -78,9 +84,9 @@ constexpr bool contains_type(std::tuple<Types...>)
|
|||
|
||||
} // namespace
|
||||
|
||||
#define TEST_CASE(type_send, initializer) \
|
||||
#define TEST_CASE(type_send) \
|
||||
{ \
|
||||
type_send val = initializer value; \
|
||||
type_send val = value; \
|
||||
\
|
||||
BOOST_CHECK_EXCEPTION( \
|
||||
lexical_cast<std::string>(val), const char*, validate); \
|
||||
|
@ -90,146 +96,52 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_lexical_cast_throw, T, test_types)
|
|||
{
|
||||
T value = T();
|
||||
|
||||
if constexpr(contains_type<T>(test_match_types{})) {
|
||||
result = "specialized - To std::string - From integral (pointer)";
|
||||
} else {
|
||||
result = "generic";
|
||||
}
|
||||
result = "specialized - To std::string - From arithmetic";
|
||||
|
||||
TEST_CASE(T, );
|
||||
TEST_CASE(const T, );
|
||||
|
||||
TEST_CASE(T&, );
|
||||
TEST_CASE(const T&, );
|
||||
TEST_CASE(T);
|
||||
TEST_CASE(const T);
|
||||
|
||||
TEST_CASE(T*, &);
|
||||
TEST_CASE(const T*, &);
|
||||
|
||||
TEST_CASE(T* const, &);
|
||||
TEST_CASE(const T* const, &);
|
||||
TEST_CASE(T&);
|
||||
TEST_CASE(const T&);
|
||||
}
|
||||
|
||||
#undef TEST_CASE
|
||||
|
||||
using test_lexical_cast_signed_types = std::tuple<
|
||||
signed char
|
||||
, short
|
||||
, int
|
||||
, long>;
|
||||
|
||||
BOOST_AUTO_TEST_CASE_TEMPLATE(
|
||||
test_lexical_cast_signed, T, test_lexical_cast_signed_types)
|
||||
test_lexical_arethmetic_signed, T, test_arethmetic_types)
|
||||
{
|
||||
result = "specialized - To signed - From (const) char*";
|
||||
result = "specialized - To arithmetic - From string";
|
||||
|
||||
const char* value = "test";
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
value), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
const_cast<char*>(value)), const char*, validate);
|
||||
|
||||
result = "specialized - To signed - From std::string";
|
||||
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
std::string(value)), const char*, validate);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_lexical_cast_long_long)
|
||||
{
|
||||
result = "specialized - To long long - From (const) char*";
|
||||
|
||||
const char* value = "test";
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<long long>(
|
||||
value), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<long long>(
|
||||
const_cast<char*>(value)), const char*, validate);
|
||||
|
||||
result = "specialized - To long long - From std::string";
|
||||
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<long long>(
|
||||
std::string(value)), const char*, validate);
|
||||
}
|
||||
|
||||
using test_lexical_cast_unsigned_types = std::tuple<
|
||||
unsigned char
|
||||
, unsigned short
|
||||
, unsigned int
|
||||
, unsigned long>;
|
||||
|
||||
BOOST_AUTO_TEST_CASE_TEMPLATE(
|
||||
test_lexical_cast_unsigned, T, test_lexical_cast_unsigned_types)
|
||||
{
|
||||
result = "specialized - To unsigned - From (const) char*";
|
||||
|
||||
const char* value = "test";
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
value), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
const_cast<char*>(value)), const char*, validate);
|
||||
|
||||
result = "specialized - To unsigned - From std::string";
|
||||
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
std::string(value)), const char*, validate);
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_lexical_cast_unsigned_long_long)
|
||||
{
|
||||
result = "specialized - To unsigned long long - From (const) char*";
|
||||
|
||||
const char* value = "test";
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<unsigned long long>(
|
||||
value), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<unsigned long long>(
|
||||
const_cast<char*>(value)), const char*, validate);
|
||||
|
||||
result = "specialized - To unsigned long long - From std::string";
|
||||
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<unsigned long long>(
|
||||
std::string(value)), const char*, validate);
|
||||
std::string_view(value)), const char*, validate);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_lexical_cast_bool)
|
||||
{
|
||||
result = "specialized - To bool - From (const) char*";
|
||||
result = "specialized - To bool - From string";
|
||||
|
||||
const char* value = "test";
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<bool>(
|
||||
value), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<bool>(
|
||||
const_cast<char*>(value)), const char*, validate);
|
||||
|
||||
result = "specialized - To bool - From std::string";
|
||||
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<bool>(
|
||||
std::string(value)), const char*, validate);
|
||||
}
|
||||
|
||||
using test_lexical_cast_floating_point_types = std::tuple<
|
||||
float
|
||||
, double
|
||||
, long double>;
|
||||
|
||||
BOOST_AUTO_TEST_CASE_TEMPLATE(
|
||||
test_lexical_cast_floating_point, T, test_lexical_cast_floating_point_types)
|
||||
{
|
||||
result = "specialized - To floating point - From (const) char*";
|
||||
|
||||
const char* value = "test";
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
value), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
const_cast<char*>(value)), const char*, validate);
|
||||
|
||||
result = "specialized - To floating point - From std::string";
|
||||
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
std::string(value)), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<bool>(
|
||||
std::string_view(value)), const char*, validate);
|
||||
}
|
||||
|
||||
} // namespace test_throw
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_lexical_cast_result)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(lexical_cast<std::string>(true), "1");
|
||||
|
@ -245,7 +157,11 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_result)
|
|||
BOOST_CHECK_EQUAL(lexical_cast<int>("-1"), -1);
|
||||
BOOST_CHECK_EQUAL(lexical_cast<unsigned>("1"), 1);
|
||||
BOOST_CHECK_EQUAL(lexical_cast<double>("1.2"), 1.2);
|
||||
BOOST_CHECK_THROW(lexical_cast<double>("0x11"), bad_lexical_cast);
|
||||
|
||||
// The unit [effect] code uses this a lot
|
||||
BOOST_CHECK_EQUAL(lexical_cast_default<int>("80%"), 80);
|
||||
|
||||
BOOST_CHECK_EQUAL(lexical_cast<double>("0x11"), 0);
|
||||
|
||||
std::string a = "01234567890123456789";
|
||||
BOOST_CHECK_EQUAL(lexical_cast<long long>(a), 1234567890123456789ll);
|
||||
|
|
79
src/utils/charconv.cpp
Normal file
79
src/utils/charconv.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright (C) 2024
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "utils/charconv.hpp"
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#ifdef USE_FALLBACK_CHARCONV
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
T string_to_floating_point(const char* str, char** str_end) = delete;
|
||||
template<>
|
||||
float string_to_floating_point(const char* str, char** str_end)
|
||||
{
|
||||
return std::strtof(str, str_end);
|
||||
}
|
||||
template<>
|
||||
double string_to_floating_point(const char* str, char** str_end)
|
||||
{
|
||||
return std::strtod(str, str_end);
|
||||
}
|
||||
template<>
|
||||
long double string_to_floating_point(const char* str, char** str_end)
|
||||
{
|
||||
return std::strtold(str, str_end);
|
||||
}
|
||||
}
|
||||
namespace utils::charconv
|
||||
{
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_same_v<T, double> || std::is_same_v<T, float> || std::is_same_v<T, long double>, from_chars_result> from_chars(const char* first, const char* last, T& value, chars_format fmt)
|
||||
{
|
||||
// We don't need the other formats.
|
||||
assert(fmt = chars_format::general);
|
||||
|
||||
// Make a copy to make sure its null terminated.
|
||||
std::string buffer = std::string(first, last);
|
||||
// from_chars doesnt support leading whitespace or plus signs.
|
||||
if(buffer.empty() || std::isspace(buffer.front()) || buffer.front() == '+' ) {
|
||||
return { first, std::errc::invalid_argument };
|
||||
}
|
||||
|
||||
|
||||
locale_t l = newlocale(LC_ALL, "C", static_cast<locale_t>(0));
|
||||
locale_t l_prev = uselocale(l);
|
||||
|
||||
//initilize this to silence a warning.
|
||||
char* str_end = nullptr;
|
||||
value = string_to_floating_point<T>( buffer.data(), &str_end );
|
||||
|
||||
uselocale(l_prev);
|
||||
freelocale(l);
|
||||
|
||||
if(str_end == buffer.data()) {
|
||||
return { first, std::errc::invalid_argument };
|
||||
}
|
||||
return { first + (str_end - buffer.data()), std::errc() };
|
||||
}
|
||||
template from_chars_result from_chars(const char* first, const char* last, float& value, chars_format fmt);
|
||||
template from_chars_result from_chars(const char* first, const char* last, double& value, chars_format fmt);
|
||||
template from_chars_result from_chars(const char* first, const char* last, long double& value, chars_format fmt);
|
||||
}
|
||||
|
||||
#endif
|
137
src/utils/charconv.hpp
Normal file
137
src/utils/charconv.hpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
Copyright (C) 2024
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/version.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <assert.h>
|
||||
#include <cctype>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
|
||||
// The gcc implemenetation of from_chars is in some versions just a temporaty solution that calls
|
||||
// strtod that's why we prefer the boost version if available.
|
||||
#if BOOST_VERSION >= 108500 && __has_include(<boost/charconv.hpp>)
|
||||
#define USE_BOOST_CHARCONV
|
||||
#elif defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE >= 11
|
||||
#define USE_STD_CHARCONV
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1924
|
||||
#define USE_STD_CHARCONV
|
||||
#elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 14000
|
||||
#define USE_FALLBACK_CHARCONV
|
||||
#else
|
||||
#error No charconv implementation found.
|
||||
#endif
|
||||
|
||||
#ifdef USE_BOOST_CHARCONV
|
||||
#include <boost/charconv.hpp>
|
||||
namespace charconv_impl = boost::charconv;
|
||||
#else
|
||||
#include <charconv>
|
||||
namespace charconv_impl = std;
|
||||
#endif
|
||||
|
||||
|
||||
namespace utils::charconv
|
||||
{
|
||||
using chars_format = charconv_impl::chars_format;
|
||||
using from_chars_result = charconv_impl::from_chars_result;
|
||||
using to_chars_result = charconv_impl::to_chars_result;
|
||||
|
||||
template<typename... T>
|
||||
to_chars_result to_chars(char* first, char* last, T&&... value )
|
||||
{
|
||||
return charconv_impl::to_chars(first, last, value...);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_integral_v<T>, from_chars_result> from_chars(const char* first, const char* last, T& value, int base = 10 )
|
||||
{
|
||||
return charconv_impl::from_chars(first, last, value, base);
|
||||
}
|
||||
|
||||
#ifndef USE_FALLBACK_CHARCONV
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_floating_point_v<T>, from_chars_result> from_chars(const char* first, const char* last, T& value, chars_format fmt = chars_format::general )
|
||||
{
|
||||
return charconv_impl::from_chars(first, last, value, fmt);
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_same_v<T, double> || std::is_same_v<T, float> || std::is_same_v<T, long double>, from_chars_result> from_chars(const char* first, const char* last, T& value, chars_format fmt = chars_format::general );
|
||||
#endif
|
||||
|
||||
// the maximum size of a string that to_chars produces for type T, with the default chars_format
|
||||
template<class T>
|
||||
constexpr size_t buffer_size = 50;
|
||||
}
|
||||
|
||||
namespace utils
|
||||
{
|
||||
// from_chars doesnt support leading whitespaces, call this before from_chars if you want to accept leading whitespaces.
|
||||
inline void trim_for_from_chars(std::string_view& v)
|
||||
{
|
||||
while(!v.empty() && std::isspace(v.front())) {
|
||||
v.remove_prefix(1);
|
||||
}
|
||||
if(v.size() >= 2 && v[0] == '+' && v[1] != '-' ) {
|
||||
v.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// converts a number to a char buffer without allocations.
|
||||
template<typename TNum>
|
||||
struct charconv_buffer
|
||||
{
|
||||
std::array<char, utils::charconv::buffer_size<TNum>> buffer;
|
||||
size_t size;
|
||||
|
||||
charconv_buffer()
|
||||
: size(0)
|
||||
{
|
||||
}
|
||||
|
||||
charconv_buffer(TNum num)
|
||||
: size(0)
|
||||
{
|
||||
set_value(num);
|
||||
}
|
||||
|
||||
void set_value(TNum num)
|
||||
{
|
||||
auto [ptr, ec] = utils::charconv::to_chars(buffer.data(), buffer.data() + buffer.size(), num);
|
||||
if(ec != std::errc()) {
|
||||
// Shouldnt happen by definition of utils::charconv::buffer_size<TNum>
|
||||
assert(!"Error in charconv_buffer, buffer not large enough");
|
||||
size = 0;
|
||||
} else {
|
||||
size = ptr - buffer.data();
|
||||
}
|
||||
//TODO: should we make this null-terminated?
|
||||
}
|
||||
|
||||
std::string_view get_view() const
|
||||
{
|
||||
return std::string_view(buffer.data(), size);
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
return std::string(buffer.data(), size);
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user