Commit 3897109e5139c88ec99cb9b5a68a28934459052e

Authored by captainurist
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>
README.md
@@ -1435,11 +1435,10 @@ provide a custom `operator&gt;&gt;` with an `istream` (inside the CLI namespace is @@ -1435,11 +1435,10 @@ provide a custom `operator&gt;&gt;` 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 &amp;input, T &amp;output) { @@ -966,18 +966,18 @@ bool lexical_cast(const std::string &amp;input, T &amp;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 &lt;typename AssignTo, @@ -1198,7 +1198,7 @@ template &lt;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 &lt; @@ -1214,7 +1214,7 @@ template &lt;
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&lt;std::string&gt; &amp;strings, AssignTo &amp;outpu @@ -1292,7 +1292,7 @@ bool lexical_conversion(const std::vector&lt;std::string&gt; &amp;strings, AssignTo &amp;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&lt;std::string&gt; &amp;values) { @@ -1556,7 +1556,7 @@ inline std::string sum_string_vector(const std::vector&lt;std::string&gt; &amp;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 &lt;typename DesiredType&gt; class TypeValidator : public Validator { @@ -270,8 +270,9 @@ template &lt;typename DesiredType&gt; 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 &amp;arg, char string @@ -31,8 +31,9 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &amp;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(&quot;IPV4&quot;) { @@ -219,7 +219,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator(&quot;IPV4&quot;) {
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 &lt;&gt; bool lexical_cast&lt;spair&gt;(const std::string &amp;input, spair &amp;output) { @@ -178,8 +174,6 @@ template &lt;&gt; bool lexical_cast&lt;spair&gt;(const std::string &amp;input, spair &amp;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, &quot;custom_string_converterFail&quot;, &quot;[newparse]&quot;) { @@ -201,6 +195,96 @@ TEST_CASE_METHOD(TApp, &quot;custom_string_converterFail&quot;, &quot;[newparse]&quot;) {
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 {