Commit 3897109e5139c88ec99cb9b5a68a28934459052e
Committed by
GitHub
1 parent
fbe17636
Using ADL everywhere for lexical_cast (#820)
* Using ADL everywhere for lexical_cast * Fixes in docs * Add a test for old extension mechanism * style: pre-commit.ci fixes * Make gcc happy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Philip Top <phlptp@gmail.com>
Showing
9 changed files
with
135 additions
and
38 deletions
README.md
| ... | ... | @@ -1435,11 +1435,10 @@ provide a custom `operator>>` with an `istream` (inside the CLI namespace is |
| 1435 | 1435 | fine if you don't want to interfere with an existing `operator>>`). |
| 1436 | 1436 | |
| 1437 | 1437 | If you wanted to extend this to support a completely new type, use a lambda or |
| 1438 | -add a specialization of the `lexical_cast` function template in the namespace of | |
| 1439 | -the type you need to convert to. Some examples of some new parsers for | |
| 1440 | -`complex<double>` that support all of the features of a standard `add_options` | |
| 1441 | -call are in [one of the tests](./tests/NewParseTest.cpp). A simpler example is | |
| 1442 | -shown below: | |
| 1438 | +add an overload of the `lexical_cast` function in the namespace of the type you | |
| 1439 | +need to convert to. Some examples of some new parsers for `complex<double>` that | |
| 1440 | +support all of the features of a standard `add_options` call are in | |
| 1441 | +[one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: | |
| 1443 | 1442 | |
| 1444 | 1443 | #### Example |
| 1445 | 1444 | ... | ... |
book/chapters/internals.md
| ... | ... | @@ -8,9 +8,9 @@ classes or inheritance. This is accomplished through lambda functions. |
| 8 | 8 | This looks like: |
| 9 | 9 | |
| 10 | 10 | ```cpp |
| 11 | -Option* add_option(string name, T item) { | |
| 11 | +Option* add_option(string name, T &item) { | |
| 12 | 12 | this->function = [&item](string value){ |
| 13 | - item = detail::lexical_cast<T>(value); | |
| 13 | + return lexical_cast(value, item); | |
| 14 | 14 | } |
| 15 | 15 | } |
| 16 | 16 | ``` | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -626,7 +626,8 @@ class App { |
| 626 | 626 | std::string flag_description = "") { |
| 627 | 627 | |
| 628 | 628 | CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { |
| 629 | - return CLI::detail::lexical_cast(res[0], flag_result); | |
| 629 | + using CLI::detail::lexical_cast; | |
| 630 | + return lexical_cast(res[0], flag_result); | |
| 630 | 631 | }; |
| 631 | 632 | auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); |
| 632 | 633 | return detail::default_flag_modifiers<T>(opt); |
| ... | ... | @@ -642,8 +643,9 @@ class App { |
| 642 | 643 | CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { |
| 643 | 644 | bool retval = true; |
| 644 | 645 | for(const auto &elem : res) { |
| 646 | + using CLI::detail::lexical_cast; | |
| 645 | 647 | flag_results.emplace_back(); |
| 646 | - retval &= detail::lexical_cast(elem, flag_results.back()); | |
| 648 | + retval &= lexical_cast(elem, flag_results.back()); | |
| 647 | 649 | } |
| 648 | 650 | return retval; |
| 649 | 651 | }; | ... | ... |
include/CLI/TypeTools.hpp
| ... | ... | @@ -966,18 +966,18 @@ bool lexical_cast(const std::string &input, T &output) { |
| 966 | 966 | bool worked = false; |
| 967 | 967 | auto nloc = str1.find_last_of("+-"); |
| 968 | 968 | if(nloc != std::string::npos && nloc > 0) { |
| 969 | - worked = detail::lexical_cast(str1.substr(0, nloc), x); | |
| 969 | + worked = lexical_cast(str1.substr(0, nloc), x); | |
| 970 | 970 | str1 = str1.substr(nloc); |
| 971 | 971 | if(str1.back() == 'i' || str1.back() == 'j') |
| 972 | 972 | str1.pop_back(); |
| 973 | - worked = worked && detail::lexical_cast(str1, y); | |
| 973 | + worked = worked && lexical_cast(str1, y); | |
| 974 | 974 | } else { |
| 975 | 975 | if(str1.back() == 'i' || str1.back() == 'j') { |
| 976 | 976 | str1.pop_back(); |
| 977 | - worked = detail::lexical_cast(str1, y); | |
| 977 | + worked = lexical_cast(str1, y); | |
| 978 | 978 | x = XC{0}; |
| 979 | 979 | } else { |
| 980 | - worked = detail::lexical_cast(str1, x); | |
| 980 | + worked = lexical_cast(str1, x); | |
| 981 | 981 | y = XC{0}; |
| 982 | 982 | } |
| 983 | 983 | } |
| ... | ... | @@ -1198,7 +1198,7 @@ template <typename AssignTo, |
| 1198 | 1198 | detail::enabler> = detail::dummy> |
| 1199 | 1199 | bool lexical_assign(const std::string &input, AssignTo &output) { |
| 1200 | 1200 | ConvertTo val{}; |
| 1201 | - bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true; | |
| 1201 | + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; | |
| 1202 | 1202 | if(parse_result) { |
| 1203 | 1203 | output = val; |
| 1204 | 1204 | } |
| ... | ... | @@ -1214,7 +1214,7 @@ template < |
| 1214 | 1214 | detail::enabler> = detail::dummy> |
| 1215 | 1215 | bool lexical_assign(const std::string &input, AssignTo &output) { |
| 1216 | 1216 | ConvertTo val{}; |
| 1217 | - bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val); | |
| 1217 | + bool parse_result = input.empty() ? true : lexical_cast(input, val); | |
| 1218 | 1218 | if(parse_result) { |
| 1219 | 1219 | output = AssignTo(val); // use () form of constructor to allow some implicit conversions |
| 1220 | 1220 | } |
| ... | ... | @@ -1292,7 +1292,7 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu |
| 1292 | 1292 | if(str1.back() == 'i' || str1.back() == 'j') { |
| 1293 | 1293 | str1.pop_back(); |
| 1294 | 1294 | } |
| 1295 | - auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); | |
| 1295 | + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); | |
| 1296 | 1296 | if(worked) { |
| 1297 | 1297 | output = ConvertTo{x, y}; |
| 1298 | 1298 | } |
| ... | ... | @@ -1556,7 +1556,7 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) { |
| 1556 | 1556 | std::string output; |
| 1557 | 1557 | for(const auto &arg : values) { |
| 1558 | 1558 | double tv{0.0}; |
| 1559 | - auto comp = detail::lexical_cast<double>(arg, tv); | |
| 1559 | + auto comp = lexical_cast(arg, tv); | |
| 1560 | 1560 | if(!comp) { |
| 1561 | 1561 | try { |
| 1562 | 1562 | tv = static_cast<double>(detail::to_flag_value(arg)); | ... | ... |
include/CLI/Validators.hpp
| ... | ... | @@ -270,8 +270,9 @@ template <typename DesiredType> class TypeValidator : public Validator { |
| 270 | 270 | public: |
| 271 | 271 | explicit TypeValidator(const std::string &validator_name) |
| 272 | 272 | : Validator(validator_name, [](std::string &input_string) { |
| 273 | + using CLI::detail::lexical_cast; | |
| 273 | 274 | auto val = DesiredType(); |
| 274 | - if(!detail::lexical_cast(input_string, val)) { | |
| 275 | + if(!lexical_cast(input_string, val)) { | |
| 275 | 276 | return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>(); |
| 276 | 277 | } |
| 277 | 278 | return std::string(); |
| ... | ... | @@ -305,8 +306,9 @@ class Range : public Validator { |
| 305 | 306 | } |
| 306 | 307 | |
| 307 | 308 | func_ = [min_val, max_val](std::string &input) { |
| 309 | + using CLI::detail::lexical_cast; | |
| 308 | 310 | T val; |
| 309 | - bool converted = detail::lexical_cast(input, val); | |
| 311 | + bool converted = lexical_cast(input, val); | |
| 310 | 312 | if((!converted) || (val < min_val || val > max_val)) { |
| 311 | 313 | std::stringstream out; |
| 312 | 314 | out << "Value " << input << " not in range ["; |
| ... | ... | @@ -342,8 +344,9 @@ class Bound : public Validator { |
| 342 | 344 | description(out.str()); |
| 343 | 345 | |
| 344 | 346 | func_ = [min_val, max_val](std::string &input) { |
| 347 | + using CLI::detail::lexical_cast; | |
| 345 | 348 | T val; |
| 346 | - bool converted = detail::lexical_cast(input, val); | |
| 349 | + bool converted = lexical_cast(input, val); | |
| 347 | 350 | if(!converted) { |
| 348 | 351 | return std::string("Value ") + input + " could not be converted"; |
| 349 | 352 | } |
| ... | ... | @@ -534,8 +537,9 @@ class IsMember : public Validator { |
| 534 | 537 | // This is the function that validates |
| 535 | 538 | // It stores a copy of the set pointer-like, so shared_ptr will stay alive |
| 536 | 539 | func_ = [set, filter_fn](std::string &input) { |
| 540 | + using CLI::detail::lexical_cast; | |
| 537 | 541 | local_item_t b; |
| 538 | - if(!detail::lexical_cast(input, b)) { | |
| 542 | + if(!lexical_cast(input, b)) { | |
| 539 | 543 | throw ValidationError(input); // name is added later |
| 540 | 544 | } |
| 541 | 545 | if(filter_fn) { |
| ... | ... | @@ -602,8 +606,9 @@ class Transformer : public Validator { |
| 602 | 606 | desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; |
| 603 | 607 | |
| 604 | 608 | func_ = [mapping, filter_fn](std::string &input) { |
| 609 | + using CLI::detail::lexical_cast; | |
| 605 | 610 | local_item_t b; |
| 606 | - if(!detail::lexical_cast(input, b)) { | |
| 611 | + if(!lexical_cast(input, b)) { | |
| 607 | 612 | return std::string(); |
| 608 | 613 | // there is no possible way we can match anything in the mapping if we can't convert so just return |
| 609 | 614 | } |
| ... | ... | @@ -671,8 +676,9 @@ class CheckedTransformer : public Validator { |
| 671 | 676 | desc_function_ = tfunc; |
| 672 | 677 | |
| 673 | 678 | func_ = [mapping, tfunc, filter_fn](std::string &input) { |
| 679 | + using CLI::detail::lexical_cast; | |
| 674 | 680 | local_item_t b; |
| 675 | - bool converted = detail::lexical_cast(input, b); | |
| 681 | + bool converted = lexical_cast(input, b); | |
| 676 | 682 | if(converted) { |
| 677 | 683 | if(filter_fn) { |
| 678 | 684 | b = filter_fn(b); |
| ... | ... | @@ -774,7 +780,8 @@ class AsNumberWithUnit : public Validator { |
| 774 | 780 | unit = detail::to_lower(unit); |
| 775 | 781 | } |
| 776 | 782 | if(unit.empty()) { |
| 777 | - if(!detail::lexical_cast(input, num)) { | |
| 783 | + using CLI::detail::lexical_cast; | |
| 784 | + if(!lexical_cast(input, num)) { | |
| 778 | 785 | throw ValidationError(std::string("Value ") + input + " could not be converted to " + |
| 779 | 786 | detail::type_name<Number>()); |
| 780 | 787 | } |
| ... | ... | @@ -792,7 +799,8 @@ class AsNumberWithUnit : public Validator { |
| 792 | 799 | } |
| 793 | 800 | |
| 794 | 801 | if(!input.empty()) { |
| 795 | - bool converted = detail::lexical_cast(input, num); | |
| 802 | + using CLI::detail::lexical_cast; | |
| 803 | + bool converted = lexical_cast(input, num); | |
| 796 | 804 | if(!converted) { |
| 797 | 805 | throw ValidationError(std::string("Value ") + input + " could not be converted to " + |
| 798 | 806 | detail::type_name<Number>()); | ... | ... |
include/CLI/impl/App_inl.hpp
| ... | ... | @@ -265,8 +265,9 @@ CLI11_INLINE Option *App::add_flag_callback(std::string flag_name, |
| 265 | 265 | std::string flag_description) { |
| 266 | 266 | |
| 267 | 267 | CLI::callback_t fun = [function](const CLI::results_t &res) { |
| 268 | + using CLI::detail::lexical_cast; | |
| 268 | 269 | bool trigger{false}; |
| 269 | - auto result = CLI::detail::lexical_cast(res[0], trigger); | |
| 270 | + auto result = lexical_cast(res[0], trigger); | |
| 270 | 271 | if(result && trigger) { |
| 271 | 272 | function(); |
| 272 | 273 | } |
| ... | ... | @@ -281,8 +282,9 @@ App::add_flag_function(std::string flag_name, |
| 281 | 282 | std::string flag_description) { |
| 282 | 283 | |
| 283 | 284 | CLI::callback_t fun = [function](const CLI::results_t &res) { |
| 285 | + using CLI::detail::lexical_cast; | |
| 284 | 286 | std::int64_t flag_count{0}; |
| 285 | - CLI::detail::lexical_cast(res[0], flag_count); | |
| 287 | + lexical_cast(res[0], flag_count); | |
| 286 | 288 | function(flag_count); |
| 287 | 289 | return true; |
| 288 | 290 | }; | ... | ... |
include/CLI/impl/Config_inl.hpp
| ... | ... | @@ -31,8 +31,9 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string |
| 31 | 31 | } |
| 32 | 32 | // floating point conversion can convert some hex codes, but don't try that here |
| 33 | 33 | if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { |
| 34 | + using CLI::detail::lexical_cast; | |
| 34 | 35 | double val = 0.0; |
| 35 | - if(detail::lexical_cast(arg, val)) { | |
| 36 | + if(lexical_cast(arg, val)) { | |
| 36 | 37 | return arg; |
| 37 | 38 | } |
| 38 | 39 | } | ... | ... |
include/CLI/impl/Validators_inl.hpp
| ... | ... | @@ -219,7 +219,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { |
| 219 | 219 | } |
| 220 | 220 | int num = 0; |
| 221 | 221 | for(const auto &var : result) { |
| 222 | - bool retval = detail::lexical_cast(var, num); | |
| 222 | + using CLI::detail::lexical_cast; | |
| 223 | + bool retval = lexical_cast(var, num); | |
| 223 | 224 | if(!retval) { |
| 224 | 225 | return std::string("Failed parsing number (") + var + ')'; |
| 225 | 226 | } | ... | ... |
tests/NewParseTest.cpp
| ... | ... | @@ -163,14 +163,10 @@ class spair { |
| 163 | 163 | std::string first{}; |
| 164 | 164 | std::string second{}; |
| 165 | 165 | }; |
| 166 | -// an example of custom converter that can be used to add new parsing options | |
| 167 | -// On MSVC and possibly some other new compilers this can be a free standing function without the template | |
| 168 | -// specialization but this is compiler dependent | |
| 169 | -namespace CLI { | |
| 170 | -namespace detail { | |
| 171 | - | |
| 172 | -template <> bool lexical_cast<spair>(const std::string &input, spair &output) { | |
| 173 | 166 | |
| 167 | +// Example of a custom converter that can be used to add new parsing options. | |
| 168 | +// It will be found via argument-dependent lookup, so should be in the same namespace as the `spair` type. | |
| 169 | +bool lexical_cast(const std::string &input, spair &output) { | |
| 174 | 170 | auto sep = input.find_first_of(':'); |
| 175 | 171 | if((sep == std::string::npos) && (sep > 0)) { |
| 176 | 172 | return false; |
| ... | ... | @@ -178,8 +174,6 @@ template <> bool lexical_cast<spair>(const std::string &input, spair &output) { |
| 178 | 174 | output = {input.substr(0, sep), input.substr(sep + 1)}; |
| 179 | 175 | return true; |
| 180 | 176 | } |
| 181 | -} // namespace detail | |
| 182 | -} // namespace CLI | |
| 183 | 177 | |
| 184 | 178 | TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") { |
| 185 | 179 | spair val; |
| ... | ... | @@ -201,6 +195,96 @@ TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") { |
| 201 | 195 | CHECK_THROWS_AS(run(), CLI::ConversionError); |
| 202 | 196 | } |
| 203 | 197 | |
| 198 | +/// Wrapper with an unconvenient interface | |
| 199 | +template <class T> class badlywrapped { | |
| 200 | + public: | |
| 201 | + badlywrapped() : value() {} | |
| 202 | + | |
| 203 | + CLI11_NODISCARD T get() const { return value; } | |
| 204 | + | |
| 205 | + void set(T val) { value = val; } | |
| 206 | + | |
| 207 | + private: | |
| 208 | + T value; | |
| 209 | +}; | |
| 210 | + | |
| 211 | +// Example of a custom converter for a template type. | |
| 212 | +// It will be found via argument-dependent lookup, so should be in the same namespace as the `badlywrapped` type. | |
| 213 | +template <class T> bool lexical_cast(const std::string &input, badlywrapped<T> &output) { | |
| 214 | + // This using declaration lets us use an unqualified call to lexical_cast below. This is important because | |
| 215 | + // unqualified call finds the proper overload via argument-dependent lookup, and thus it will be able to find | |
| 216 | + // an overload for `spair` type, which is not in `CLI::detail`. | |
| 217 | + using CLI::detail::lexical_cast; | |
| 218 | + | |
| 219 | + T value; | |
| 220 | + if(!lexical_cast(input, value)) | |
| 221 | + return false; | |
| 222 | + output.set(value); | |
| 223 | + return true; | |
| 224 | +} | |
| 225 | + | |
| 226 | +TEST_CASE_METHOD(TApp, "custom_string_converter_flag", "[newparse]") { | |
| 227 | + badlywrapped<bool> val; | |
| 228 | + std::vector<badlywrapped<bool>> vals; | |
| 229 | + app.add_flag("-1", val); | |
| 230 | + app.add_flag("-2", vals); | |
| 231 | + | |
| 232 | + val.set(false); | |
| 233 | + args = {"-1"}; | |
| 234 | + run(); | |
| 235 | + CHECK(true == val.get()); | |
| 236 | + | |
| 237 | + args = {"-2", "-2"}; | |
| 238 | + run(); | |
| 239 | + CHECK(2 == vals.size()); | |
| 240 | + CHECK(true == vals[0].get()); | |
| 241 | + CHECK(true == vals[1].get()); | |
| 242 | +} | |
| 243 | + | |
| 244 | +TEST_CASE_METHOD(TApp, "custom_string_converter_adl", "[newparse]") { | |
| 245 | + // This test checks that the lexical_cast calls route as expected. | |
| 246 | + badlywrapped<spair> val; | |
| 247 | + | |
| 248 | + app.add_option("-d,--dual_string", val); | |
| 249 | + | |
| 250 | + args = {"-d", "string1:string2"}; | |
| 251 | + | |
| 252 | + run(); | |
| 253 | + CHECK("string1" == val.get().first); | |
| 254 | + CHECK("string2" == val.get().second); | |
| 255 | +} | |
| 256 | + | |
| 257 | +/// Another wrapper to test that specializing CLI::detail::lexical_cast works | |
| 258 | +struct anotherstring { | |
| 259 | + anotherstring() = default; | |
| 260 | + std::string s{}; | |
| 261 | +}; | |
| 262 | + | |
| 263 | +// This is a custom converter done via specializing the CLI::detail::lexical_cast template. This was the recommended | |
| 264 | +// mechanism for extending the library before, so we need to test it. Don't do this in your code, use | |
| 265 | +// argument-dependent lookup as outlined in the examples for spair and template badlywrapped. | |
| 266 | +namespace CLI { | |
| 267 | +namespace detail { | |
| 268 | +template <> bool lexical_cast<anotherstring>(const std::string &input, anotherstring &output) { | |
| 269 | + bool result = lexical_cast(input, output.s); | |
| 270 | + if(result) | |
| 271 | + output.s += "!"; | |
| 272 | + return result; | |
| 273 | +} | |
| 274 | +} // namespace detail | |
| 275 | +} // namespace CLI | |
| 276 | + | |
| 277 | +TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") { | |
| 278 | + anotherstring s; | |
| 279 | + | |
| 280 | + app.add_option("-s", s); | |
| 281 | + | |
| 282 | + args = {"-s", "something"}; | |
| 283 | + | |
| 284 | + run(); | |
| 285 | + CHECK("something!" == s.s); | |
| 286 | +} | |
| 287 | + | |
| 204 | 288 | /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the |
| 205 | 289 | /// option assignments |
| 206 | 290 | template <class X> class objWrapper { | ... | ... |