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 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 &amp;input, T &amp;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 &lt;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 &lt;
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&lt;std::string&gt; &amp;strings, AssignTo &amp;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&lt;std::string&gt; &amp;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 &lt;typename DesiredType&gt; 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 &amp;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(&quot;IPV4&quot;) {
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 &lt;&gt; bool lexical_cast&lt;spair&gt;(const std::string &amp;input, spair &amp;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, &quot;custom_string_converterFail&quot;, &quot;[newparse]&quot;) {
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 {
... ...