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,11 +1435,10 @@ provide a custom `operator>>` with an `istream` (inside the CLI namespace is | ||
| 1435 | fine if you don't want to interfere with an existing `operator>>`). | 1435 | fine if you don't want to interfere with an existing `operator>>`). |
| 1436 | 1436 | ||
| 1437 | If you wanted to extend this to support a completely new type, use a lambda or | 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 | #### Example | 1443 | #### Example |
| 1445 | 1444 |
book/chapters/internals.md
| @@ -8,9 +8,9 @@ classes or inheritance. This is accomplished through lambda functions. | @@ -8,9 +8,9 @@ classes or inheritance. This is accomplished through lambda functions. | ||
| 8 | This looks like: | 8 | This looks like: |
| 9 | 9 | ||
| 10 | ```cpp | 10 | ```cpp |
| 11 | -Option* add_option(string name, T item) { | 11 | +Option* add_option(string name, T &item) { |
| 12 | this->function = [&item](string value){ | 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,7 +626,8 @@ class App { | ||
| 626 | std::string flag_description = "") { | 626 | std::string flag_description = "") { |
| 627 | 627 | ||
| 628 | CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { | 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 | auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); | 632 | auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); |
| 632 | return detail::default_flag_modifiers<T>(opt); | 633 | return detail::default_flag_modifiers<T>(opt); |
| @@ -642,8 +643,9 @@ class App { | @@ -642,8 +643,9 @@ class App { | ||
| 642 | CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { | 643 | CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { |
| 643 | bool retval = true; | 644 | bool retval = true; |
| 644 | for(const auto &elem : res) { | 645 | for(const auto &elem : res) { |
| 646 | + using CLI::detail::lexical_cast; | ||
| 645 | flag_results.emplace_back(); | 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 | return retval; | 650 | return retval; |
| 649 | }; | 651 | }; |
include/CLI/TypeTools.hpp
| @@ -966,18 +966,18 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -966,18 +966,18 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 966 | bool worked = false; | 966 | bool worked = false; |
| 967 | auto nloc = str1.find_last_of("+-"); | 967 | auto nloc = str1.find_last_of("+-"); |
| 968 | if(nloc != std::string::npos && nloc > 0) { | 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 | str1 = str1.substr(nloc); | 970 | str1 = str1.substr(nloc); |
| 971 | if(str1.back() == 'i' || str1.back() == 'j') | 971 | if(str1.back() == 'i' || str1.back() == 'j') |
| 972 | str1.pop_back(); | 972 | str1.pop_back(); |
| 973 | - worked = worked && detail::lexical_cast(str1, y); | 973 | + worked = worked && lexical_cast(str1, y); |
| 974 | } else { | 974 | } else { |
| 975 | if(str1.back() == 'i' || str1.back() == 'j') { | 975 | if(str1.back() == 'i' || str1.back() == 'j') { |
| 976 | str1.pop_back(); | 976 | str1.pop_back(); |
| 977 | - worked = detail::lexical_cast(str1, y); | 977 | + worked = lexical_cast(str1, y); |
| 978 | x = XC{0}; | 978 | x = XC{0}; |
| 979 | } else { | 979 | } else { |
| 980 | - worked = detail::lexical_cast(str1, x); | 980 | + worked = lexical_cast(str1, x); |
| 981 | y = XC{0}; | 981 | y = XC{0}; |
| 982 | } | 982 | } |
| 983 | } | 983 | } |
| @@ -1198,7 +1198,7 @@ template <typename AssignTo, | @@ -1198,7 +1198,7 @@ template <typename AssignTo, | ||
| 1198 | detail::enabler> = detail::dummy> | 1198 | detail::enabler> = detail::dummy> |
| 1199 | bool lexical_assign(const std::string &input, AssignTo &output) { | 1199 | bool lexical_assign(const std::string &input, AssignTo &output) { |
| 1200 | ConvertTo val{}; | 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 | if(parse_result) { | 1202 | if(parse_result) { |
| 1203 | output = val; | 1203 | output = val; |
| 1204 | } | 1204 | } |
| @@ -1214,7 +1214,7 @@ template < | @@ -1214,7 +1214,7 @@ template < | ||
| 1214 | detail::enabler> = detail::dummy> | 1214 | detail::enabler> = detail::dummy> |
| 1215 | bool lexical_assign(const std::string &input, AssignTo &output) { | 1215 | bool lexical_assign(const std::string &input, AssignTo &output) { |
| 1216 | ConvertTo val{}; | 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 | if(parse_result) { | 1218 | if(parse_result) { |
| 1219 | output = AssignTo(val); // use () form of constructor to allow some implicit conversions | 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,7 +1292,7 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu | ||
| 1292 | if(str1.back() == 'i' || str1.back() == 'j') { | 1292 | if(str1.back() == 'i' || str1.back() == 'j') { |
| 1293 | str1.pop_back(); | 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 | if(worked) { | 1296 | if(worked) { |
| 1297 | output = ConvertTo{x, y}; | 1297 | output = ConvertTo{x, y}; |
| 1298 | } | 1298 | } |
| @@ -1556,7 +1556,7 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) { | @@ -1556,7 +1556,7 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) { | ||
| 1556 | std::string output; | 1556 | std::string output; |
| 1557 | for(const auto &arg : values) { | 1557 | for(const auto &arg : values) { |
| 1558 | double tv{0.0}; | 1558 | double tv{0.0}; |
| 1559 | - auto comp = detail::lexical_cast<double>(arg, tv); | 1559 | + auto comp = lexical_cast(arg, tv); |
| 1560 | if(!comp) { | 1560 | if(!comp) { |
| 1561 | try { | 1561 | try { |
| 1562 | tv = static_cast<double>(detail::to_flag_value(arg)); | 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,8 +270,9 @@ template <typename DesiredType> class TypeValidator : public Validator { | ||
| 270 | public: | 270 | public: |
| 271 | explicit TypeValidator(const std::string &validator_name) | 271 | explicit TypeValidator(const std::string &validator_name) |
| 272 | : Validator(validator_name, [](std::string &input_string) { | 272 | : Validator(validator_name, [](std::string &input_string) { |
| 273 | + using CLI::detail::lexical_cast; | ||
| 273 | auto val = DesiredType(); | 274 | auto val = DesiredType(); |
| 274 | - if(!detail::lexical_cast(input_string, val)) { | 275 | + if(!lexical_cast(input_string, val)) { |
| 275 | return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>(); | 276 | return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>(); |
| 276 | } | 277 | } |
| 277 | return std::string(); | 278 | return std::string(); |
| @@ -305,8 +306,9 @@ class Range : public Validator { | @@ -305,8 +306,9 @@ class Range : public Validator { | ||
| 305 | } | 306 | } |
| 306 | 307 | ||
| 307 | func_ = [min_val, max_val](std::string &input) { | 308 | func_ = [min_val, max_val](std::string &input) { |
| 309 | + using CLI::detail::lexical_cast; | ||
| 308 | T val; | 310 | T val; |
| 309 | - bool converted = detail::lexical_cast(input, val); | 311 | + bool converted = lexical_cast(input, val); |
| 310 | if((!converted) || (val < min_val || val > max_val)) { | 312 | if((!converted) || (val < min_val || val > max_val)) { |
| 311 | std::stringstream out; | 313 | std::stringstream out; |
| 312 | out << "Value " << input << " not in range ["; | 314 | out << "Value " << input << " not in range ["; |
| @@ -342,8 +344,9 @@ class Bound : public Validator { | @@ -342,8 +344,9 @@ class Bound : public Validator { | ||
| 342 | description(out.str()); | 344 | description(out.str()); |
| 343 | 345 | ||
| 344 | func_ = [min_val, max_val](std::string &input) { | 346 | func_ = [min_val, max_val](std::string &input) { |
| 347 | + using CLI::detail::lexical_cast; | ||
| 345 | T val; | 348 | T val; |
| 346 | - bool converted = detail::lexical_cast(input, val); | 349 | + bool converted = lexical_cast(input, val); |
| 347 | if(!converted) { | 350 | if(!converted) { |
| 348 | return std::string("Value ") + input + " could not be converted"; | 351 | return std::string("Value ") + input + " could not be converted"; |
| 349 | } | 352 | } |
| @@ -534,8 +537,9 @@ class IsMember : public Validator { | @@ -534,8 +537,9 @@ class IsMember : public Validator { | ||
| 534 | // This is the function that validates | 537 | // This is the function that validates |
| 535 | // It stores a copy of the set pointer-like, so shared_ptr will stay alive | 538 | // It stores a copy of the set pointer-like, so shared_ptr will stay alive |
| 536 | func_ = [set, filter_fn](std::string &input) { | 539 | func_ = [set, filter_fn](std::string &input) { |
| 540 | + using CLI::detail::lexical_cast; | ||
| 537 | local_item_t b; | 541 | local_item_t b; |
| 538 | - if(!detail::lexical_cast(input, b)) { | 542 | + if(!lexical_cast(input, b)) { |
| 539 | throw ValidationError(input); // name is added later | 543 | throw ValidationError(input); // name is added later |
| 540 | } | 544 | } |
| 541 | if(filter_fn) { | 545 | if(filter_fn) { |
| @@ -602,8 +606,9 @@ class Transformer : public Validator { | @@ -602,8 +606,9 @@ class Transformer : public Validator { | ||
| 602 | desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; | 606 | desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; |
| 603 | 607 | ||
| 604 | func_ = [mapping, filter_fn](std::string &input) { | 608 | func_ = [mapping, filter_fn](std::string &input) { |
| 609 | + using CLI::detail::lexical_cast; | ||
| 605 | local_item_t b; | 610 | local_item_t b; |
| 606 | - if(!detail::lexical_cast(input, b)) { | 611 | + if(!lexical_cast(input, b)) { |
| 607 | return std::string(); | 612 | return std::string(); |
| 608 | // there is no possible way we can match anything in the mapping if we can't convert so just return | 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,8 +676,9 @@ class CheckedTransformer : public Validator { | ||
| 671 | desc_function_ = tfunc; | 676 | desc_function_ = tfunc; |
| 672 | 677 | ||
| 673 | func_ = [mapping, tfunc, filter_fn](std::string &input) { | 678 | func_ = [mapping, tfunc, filter_fn](std::string &input) { |
| 679 | + using CLI::detail::lexical_cast; | ||
| 674 | local_item_t b; | 680 | local_item_t b; |
| 675 | - bool converted = detail::lexical_cast(input, b); | 681 | + bool converted = lexical_cast(input, b); |
| 676 | if(converted) { | 682 | if(converted) { |
| 677 | if(filter_fn) { | 683 | if(filter_fn) { |
| 678 | b = filter_fn(b); | 684 | b = filter_fn(b); |
| @@ -774,7 +780,8 @@ class AsNumberWithUnit : public Validator { | @@ -774,7 +780,8 @@ class AsNumberWithUnit : public Validator { | ||
| 774 | unit = detail::to_lower(unit); | 780 | unit = detail::to_lower(unit); |
| 775 | } | 781 | } |
| 776 | if(unit.empty()) { | 782 | if(unit.empty()) { |
| 777 | - if(!detail::lexical_cast(input, num)) { | 783 | + using CLI::detail::lexical_cast; |
| 784 | + if(!lexical_cast(input, num)) { | ||
| 778 | throw ValidationError(std::string("Value ") + input + " could not be converted to " + | 785 | throw ValidationError(std::string("Value ") + input + " could not be converted to " + |
| 779 | detail::type_name<Number>()); | 786 | detail::type_name<Number>()); |
| 780 | } | 787 | } |
| @@ -792,7 +799,8 @@ class AsNumberWithUnit : public Validator { | @@ -792,7 +799,8 @@ class AsNumberWithUnit : public Validator { | ||
| 792 | } | 799 | } |
| 793 | 800 | ||
| 794 | if(!input.empty()) { | 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 | if(!converted) { | 804 | if(!converted) { |
| 797 | throw ValidationError(std::string("Value ") + input + " could not be converted to " + | 805 | throw ValidationError(std::string("Value ") + input + " could not be converted to " + |
| 798 | detail::type_name<Number>()); | 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,8 +265,9 @@ CLI11_INLINE Option *App::add_flag_callback(std::string flag_name, | ||
| 265 | std::string flag_description) { | 265 | std::string flag_description) { |
| 266 | 266 | ||
| 267 | CLI::callback_t fun = [function](const CLI::results_t &res) { | 267 | CLI::callback_t fun = [function](const CLI::results_t &res) { |
| 268 | + using CLI::detail::lexical_cast; | ||
| 268 | bool trigger{false}; | 269 | bool trigger{false}; |
| 269 | - auto result = CLI::detail::lexical_cast(res[0], trigger); | 270 | + auto result = lexical_cast(res[0], trigger); |
| 270 | if(result && trigger) { | 271 | if(result && trigger) { |
| 271 | function(); | 272 | function(); |
| 272 | } | 273 | } |
| @@ -281,8 +282,9 @@ App::add_flag_function(std::string flag_name, | @@ -281,8 +282,9 @@ App::add_flag_function(std::string flag_name, | ||
| 281 | std::string flag_description) { | 282 | std::string flag_description) { |
| 282 | 283 | ||
| 283 | CLI::callback_t fun = [function](const CLI::results_t &res) { | 284 | CLI::callback_t fun = [function](const CLI::results_t &res) { |
| 285 | + using CLI::detail::lexical_cast; | ||
| 284 | std::int64_t flag_count{0}; | 286 | std::int64_t flag_count{0}; |
| 285 | - CLI::detail::lexical_cast(res[0], flag_count); | 287 | + lexical_cast(res[0], flag_count); |
| 286 | function(flag_count); | 288 | function(flag_count); |
| 287 | return true; | 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,8 +31,9 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string | ||
| 31 | } | 31 | } |
| 32 | // floating point conversion can convert some hex codes, but don't try that here | 32 | // floating point conversion can convert some hex codes, but don't try that here |
| 33 | if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { | 33 | if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { |
| 34 | + using CLI::detail::lexical_cast; | ||
| 34 | double val = 0.0; | 35 | double val = 0.0; |
| 35 | - if(detail::lexical_cast(arg, val)) { | 36 | + if(lexical_cast(arg, val)) { |
| 36 | return arg; | 37 | return arg; |
| 37 | } | 38 | } |
| 38 | } | 39 | } |
include/CLI/impl/Validators_inl.hpp
| @@ -219,7 +219,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { | @@ -219,7 +219,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { | ||
| 219 | } | 219 | } |
| 220 | int num = 0; | 220 | int num = 0; |
| 221 | for(const auto &var : result) { | 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 | if(!retval) { | 224 | if(!retval) { |
| 224 | return std::string("Failed parsing number (") + var + ')'; | 225 | return std::string("Failed parsing number (") + var + ')'; |
| 225 | } | 226 | } |
tests/NewParseTest.cpp
| @@ -163,14 +163,10 @@ class spair { | @@ -163,14 +163,10 @@ class spair { | ||
| 163 | std::string first{}; | 163 | std::string first{}; |
| 164 | std::string second{}; | 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 | auto sep = input.find_first_of(':'); | 170 | auto sep = input.find_first_of(':'); |
| 175 | if((sep == std::string::npos) && (sep > 0)) { | 171 | if((sep == std::string::npos) && (sep > 0)) { |
| 176 | return false; | 172 | return false; |
| @@ -178,8 +174,6 @@ template <> bool lexical_cast<spair>(const std::string &input, spair &output) { | @@ -178,8 +174,6 @@ template <> bool lexical_cast<spair>(const std::string &input, spair &output) { | ||
| 178 | output = {input.substr(0, sep), input.substr(sep + 1)}; | 174 | output = {input.substr(0, sep), input.substr(sep + 1)}; |
| 179 | return true; | 175 | return true; |
| 180 | } | 176 | } |
| 181 | -} // namespace detail | ||
| 182 | -} // namespace CLI | ||
| 183 | 177 | ||
| 184 | TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") { | 178 | TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") { |
| 185 | spair val; | 179 | spair val; |
| @@ -201,6 +195,96 @@ TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") { | @@ -201,6 +195,96 @@ TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") { | ||
| 201 | CHECK_THROWS_AS(run(), CLI::ConversionError); | 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 | /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the | 288 | /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the |
| 205 | /// option assignments | 289 | /// option assignments |
| 206 | template <class X> class objWrapper { | 290 | template <class X> class objWrapper { |