Commit 17ddce2fb2e76d538ec449c395ae9ae0a36ca215
Committed by
Henry Schreiner
1 parent
ba7aac9c
Add classification type traits (#286)
This cleans up the type checking a bit and makes it more readable, along with some other cleanup. * start work on trying to clean up the type traits for which lexical cast overload to use * fix readme issue and make the condition tests a little clearer * add a check for out of range errors on boolean conversions * Fix capitalization and some comments on option functions * fix a few code analysis warnings for VS2019
Showing
6 changed files
with
171 additions
and
75 deletions
README.md
| @@ -604,7 +604,7 @@ The subcommand method | @@ -604,7 +604,7 @@ The subcommand method | ||
| 604 | .add_option_group(name,description) | 604 | .add_option_group(name,description) |
| 605 | ``` | 605 | ``` |
| 606 | 606 | ||
| 607 | -Will create an option group, and return a pointer to it. An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements. They allow specific sets of options to be composed and controlled as a collective. For an example see [range test](./tests/ranges.cpp). Option groups are a specialization of an App so all [functions](#subcommand-options) that work with an App or subcommand also work on option groups. Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through | 607 | +Will create an option group, and return a pointer to it. An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements. They allow specific sets of options to be composed and controlled as a collective. For an example see [range example](https://github.com/CLIUtils/CLI11/blob/master/examples/ranges.cpp). Option groups are a specialization of an App so all [functions](#subcommand-options) that work with an App or subcommand also work on option groups. Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through |
| 608 | 608 | ||
| 609 | ```cpp | 609 | ```cpp |
| 610 | ogroup->add_option(option_pointer); | 610 | ogroup->add_option(option_pointer); |
include/CLI/App.hpp
| @@ -1292,7 +1292,7 @@ class App { | @@ -1292,7 +1292,7 @@ class App { | ||
| 1292 | } | 1292 | } |
| 1293 | 1293 | ||
| 1294 | std::vector<std::string> args; | 1294 | std::vector<std::string> args; |
| 1295 | - args.reserve(static_cast<size_t>(argc - 1)); | 1295 | + args.reserve(static_cast<size_t>(argc) - 1); |
| 1296 | for(int i = argc - 1; i > 0; i--) | 1296 | for(int i = argc - 1; i > 0; i--) |
| 1297 | args.emplace_back(argv[i]); | 1297 | args.emplace_back(argv[i]); |
| 1298 | parse(std::move(args)); | 1298 | parse(std::move(args)); |
| @@ -2317,7 +2317,7 @@ class App { | @@ -2317,7 +2317,7 @@ class App { | ||
| 2317 | 2317 | ||
| 2318 | // LCOV_EXCL_START | 2318 | // LCOV_EXCL_START |
| 2319 | default: | 2319 | default: |
| 2320 | - HorribleError("unrecognized classifier (you should not see this!)"); | 2320 | + throw HorribleError("unrecognized classifier (you should not see this!)"); |
| 2321 | // LCOV_EXCL_END | 2321 | // LCOV_EXCL_END |
| 2322 | } | 2322 | } |
| 2323 | return retval; | 2323 | return retval; |
include/CLI/Option.hpp
| @@ -527,7 +527,7 @@ class Option : public OptionBase<Option> { | @@ -527,7 +527,7 @@ class Option : public OptionBase<Option> { | ||
| 527 | return this; | 527 | return this; |
| 528 | } | 528 | } |
| 529 | 529 | ||
| 530 | - /// disable flag overrides | 530 | + /// Disable flag overrides values, e.g. --flag=<value> is not allowed |
| 531 | Option *disable_flag_override(bool value = true) { | 531 | Option *disable_flag_override(bool value = true) { |
| 532 | disable_flag_override_ = value; | 532 | disable_flag_override_ = value; |
| 533 | return this; | 533 | return this; |
| @@ -564,7 +564,7 @@ class Option : public OptionBase<Option> { | @@ -564,7 +564,7 @@ class Option : public OptionBase<Option> { | ||
| 564 | /// Get the short names | 564 | /// Get the short names |
| 565 | const std::vector<std::string> get_snames() const { return snames_; } | 565 | const std::vector<std::string> get_snames() const { return snames_; } |
| 566 | 566 | ||
| 567 | - /// get the flag names with specified default values | 567 | + /// Get the flag names with specified default values |
| 568 | const std::vector<std::string> get_fnames() const { return fnames_; } | 568 | const std::vector<std::string> get_fnames() const { return fnames_; } |
| 569 | 569 | ||
| 570 | /// The number of times the option expects to be included | 570 | /// The number of times the option expects to be included |
| @@ -790,6 +790,7 @@ class Option : public OptionBase<Option> { | @@ -790,6 +790,7 @@ class Option : public OptionBase<Option> { | ||
| 790 | return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0); | 790 | return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0); |
| 791 | } | 791 | } |
| 792 | 792 | ||
| 793 | + /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not disabled | ||
| 793 | std::string get_flag_value(std::string name, std::string input_value) const { | 794 | std::string get_flag_value(std::string name, std::string input_value) const { |
| 794 | static const std::string trueString{"true"}; | 795 | static const std::string trueString{"true"}; |
| 795 | static const std::string falseString{"false"}; | 796 | static const std::string falseString{"false"}; |
| @@ -855,7 +856,7 @@ class Option : public OptionBase<Option> { | @@ -855,7 +856,7 @@ class Option : public OptionBase<Option> { | ||
| 855 | /// Get a copy of the results | 856 | /// Get a copy of the results |
| 856 | std::vector<std::string> results() const { return results_; } | 857 | std::vector<std::string> results() const { return results_; } |
| 857 | 858 | ||
| 858 | - /// get the results as a particular type | 859 | + /// Get the results as a specified type |
| 859 | template <typename T, | 860 | template <typename T, |
| 860 | enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy> | 861 | enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy> |
| 861 | void results(T &output) const { | 862 | void results(T &output) const { |
| @@ -884,7 +885,7 @@ class Option : public OptionBase<Option> { | @@ -884,7 +885,7 @@ class Option : public OptionBase<Option> { | ||
| 884 | throw ConversionError(get_name(), results_); | 885 | throw ConversionError(get_name(), results_); |
| 885 | } | 886 | } |
| 886 | } | 887 | } |
| 887 | - /// get the results as a vector of a particular type | 888 | + /// Get the results as a vector of the specified type |
| 888 | template <typename T> void results(std::vector<T> &output) const { | 889 | template <typename T> void results(std::vector<T> &output) const { |
| 889 | output.clear(); | 890 | output.clear(); |
| 890 | bool retval = true; | 891 | bool retval = true; |
| @@ -899,7 +900,7 @@ class Option : public OptionBase<Option> { | @@ -899,7 +900,7 @@ class Option : public OptionBase<Option> { | ||
| 899 | } | 900 | } |
| 900 | } | 901 | } |
| 901 | 902 | ||
| 902 | - /// return the results as a particular type | 903 | + /// Return the results as the specified type |
| 903 | template <typename T> T as() const { | 904 | template <typename T> T as() const { |
| 904 | T output; | 905 | T output; |
| 905 | results(output); | 906 | results(output); |
| @@ -980,7 +981,7 @@ class Option : public OptionBase<Option> { | @@ -980,7 +981,7 @@ class Option : public OptionBase<Option> { | ||
| 980 | } | 981 | } |
| 981 | 982 | ||
| 982 | private: | 983 | private: |
| 983 | - // run through the validators | 984 | + // Run a result through the validators |
| 984 | std::string _validate(std::string &result) { | 985 | std::string _validate(std::string &result) { |
| 985 | std::string err_msg; | 986 | std::string err_msg; |
| 986 | for(const auto &vali : validators_) { | 987 | for(const auto &vali : validators_) { |
| @@ -995,6 +996,7 @@ class Option : public OptionBase<Option> { | @@ -995,6 +996,7 @@ class Option : public OptionBase<Option> { | ||
| 995 | return err_msg; | 996 | return err_msg; |
| 996 | } | 997 | } |
| 997 | 998 | ||
| 999 | + /// Add a single result to the result set, taking into account delimiters | ||
| 998 | int _add_result(std::string &&result) { | 1000 | int _add_result(std::string &&result) { |
| 999 | int result_count = 0; | 1001 | int result_count = 0; |
| 1000 | if(delimiter_ == '\0') { | 1002 | if(delimiter_ == '\0') { |
include/CLI/TypeTools.hpp
| @@ -228,6 +228,118 @@ std::string checked_to_string(T &&) { | @@ -228,6 +228,118 @@ std::string checked_to_string(T &&) { | ||
| 228 | return std::string{}; | 228 | return std::string{}; |
| 229 | } | 229 | } |
| 230 | 230 | ||
| 231 | +// Enumeration of the different supported categorizations of objects | ||
| 232 | +enum objCategory : int { | ||
| 233 | + integral_value = 2, | ||
| 234 | + unsigned_integral = 4, | ||
| 235 | + enumeration = 6, | ||
| 236 | + boolean_value = 8, | ||
| 237 | + floating_point = 10, | ||
| 238 | + number_constructible = 12, | ||
| 239 | + double_constructible = 14, | ||
| 240 | + integer_constructible = 16, | ||
| 241 | + vector_value = 30, | ||
| 242 | + // string assignable or greater used in a condition so anything string like must come last | ||
| 243 | + string_assignable = 50, | ||
| 244 | + string_constructible = 60, | ||
| 245 | + other = 200, | ||
| 246 | + | ||
| 247 | +}; | ||
| 248 | + | ||
| 249 | +/// some type that is not otherwise recognized | ||
| 250 | +template <typename T, typename Enable = void> struct classify_object { static constexpr objCategory value{other}; }; | ||
| 251 | + | ||
| 252 | +/// Set of overloads to classify an object according to type | ||
| 253 | +template <typename T> | ||
| 254 | +struct classify_object<T, | ||
| 255 | + typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value && | ||
| 256 | + !is_bool<T>::value && !std::is_enum<T>::value>::type> { | ||
| 257 | + static constexpr objCategory value{integral_value}; | ||
| 258 | +}; | ||
| 259 | + | ||
| 260 | +/// Unsigned integers | ||
| 261 | +template <typename T> | ||
| 262 | +struct classify_object< | ||
| 263 | + T, | ||
| 264 | + typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value>::type> { | ||
| 265 | + static constexpr objCategory value{unsigned_integral}; | ||
| 266 | +}; | ||
| 267 | + | ||
| 268 | +/// Boolean values | ||
| 269 | +template <typename T> struct classify_object<T, typename std::enable_if<is_bool<T>::value>::type> { | ||
| 270 | + static constexpr objCategory value{boolean_value}; | ||
| 271 | +}; | ||
| 272 | + | ||
| 273 | +/// Floats | ||
| 274 | +template <typename T> struct classify_object<T, typename std::enable_if<std::is_floating_point<T>::value>::type> { | ||
| 275 | + static constexpr objCategory value{floating_point}; | ||
| 276 | +}; | ||
| 277 | + | ||
| 278 | +/// String and similar direct assignment | ||
| 279 | +template <typename T> | ||
| 280 | +struct classify_object< | ||
| 281 | + T, | ||
| 282 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 283 | + std::is_assignable<T &, std::string>::value && !is_vector<T>::value>::type> { | ||
| 284 | + static constexpr objCategory value{string_assignable}; | ||
| 285 | +}; | ||
| 286 | + | ||
| 287 | +/// String and similar constructible and copy assignment | ||
| 288 | +template <typename T> | ||
| 289 | +struct classify_object< | ||
| 290 | + T, | ||
| 291 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 292 | + !std::is_assignable<T &, std::string>::value && | ||
| 293 | + std::is_constructible<T, std::string>::value && !is_vector<T>::value>::type> { | ||
| 294 | + static constexpr objCategory value{string_constructible}; | ||
| 295 | +}; | ||
| 296 | + | ||
| 297 | +/// Enumerations | ||
| 298 | +template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> { | ||
| 299 | + static constexpr objCategory value{enumeration}; | ||
| 300 | +}; | ||
| 301 | + | ||
| 302 | +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, | ||
| 303 | +/// vectors, and enumerations | ||
| 304 | +template <typename T> struct uncommon_type { | ||
| 305 | + using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 306 | + !std::is_assignable<T &, std::string>::value && | ||
| 307 | + !std::is_constructible<T, std::string>::value && !is_vector<T>::value && | ||
| 308 | + !std::is_enum<T>::value, | ||
| 309 | + std::true_type, | ||
| 310 | + std::false_type>::type; | ||
| 311 | + static const bool value = type::value; | ||
| 312 | +}; | ||
| 313 | + | ||
| 314 | +/// Assignable from double or int | ||
| 315 | +template <typename T> | ||
| 316 | +struct classify_object<T, | ||
| 317 | + typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value && | ||
| 318 | + is_direct_constructible<T, int>::value>::type> { | ||
| 319 | + static constexpr objCategory value{number_constructible}; | ||
| 320 | +}; | ||
| 321 | + | ||
| 322 | +/// Assignable from int | ||
| 323 | +template <typename T> | ||
| 324 | +struct classify_object<T, | ||
| 325 | + typename std::enable_if<uncommon_type<T>::value && !is_direct_constructible<T, double>::value && | ||
| 326 | + is_direct_constructible<T, int>::value>::type> { | ||
| 327 | + static const objCategory value{integer_constructible}; | ||
| 328 | +}; | ||
| 329 | + | ||
| 330 | +/// Assignable from double | ||
| 331 | +template <typename T> | ||
| 332 | +struct classify_object<T, | ||
| 333 | + typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value && | ||
| 334 | + !is_direct_constructible<T, int>::value>::type> { | ||
| 335 | + static const objCategory value{double_constructible}; | ||
| 336 | +}; | ||
| 337 | + | ||
| 338 | +/// vector type | ||
| 339 | +template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> { | ||
| 340 | + static const objCategory value{vector_value}; | ||
| 341 | +}; | ||
| 342 | + | ||
| 231 | // Type name print | 343 | // Type name print |
| 232 | 344 | ||
| 233 | /// Was going to be based on | 345 | /// Was going to be based on |
| @@ -235,38 +347,45 @@ std::string checked_to_string(T &&) { | @@ -235,38 +347,45 @@ std::string checked_to_string(T &&) { | ||
| 235 | /// But this is cleaner and works better in this case | 347 | /// But this is cleaner and works better in this case |
| 236 | 348 | ||
| 237 | template <typename T, | 349 | template <typename T, |
| 238 | - enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> | 350 | + enable_if_t<classify_object<T>::value == integral_value || classify_object<T>::value == integer_constructible, |
| 351 | + detail::enabler> = detail::dummy> | ||
| 239 | constexpr const char *type_name() { | 352 | constexpr const char *type_name() { |
| 240 | return "INT"; | 353 | return "INT"; |
| 241 | } | 354 | } |
| 242 | 355 | ||
| 243 | -template <typename T, | ||
| 244 | - enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> | 356 | +template <typename T, enable_if_t<classify_object<T>::value == unsigned_integral, detail::enabler> = detail::dummy> |
| 245 | constexpr const char *type_name() { | 357 | constexpr const char *type_name() { |
| 246 | return "UINT"; | 358 | return "UINT"; |
| 247 | } | 359 | } |
| 248 | 360 | ||
| 249 | -template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> | 361 | +template < |
| 362 | + typename T, | ||
| 363 | + enable_if_t<classify_object<T>::value == floating_point || classify_object<T>::value == number_constructible || | ||
| 364 | + classify_object<T>::value == double_constructible, | ||
| 365 | + detail::enabler> = detail::dummy> | ||
| 250 | constexpr const char *type_name() { | 366 | constexpr const char *type_name() { |
| 251 | return "FLOAT"; | 367 | return "FLOAT"; |
| 252 | } | 368 | } |
| 253 | 369 | ||
| 254 | /// This one should not be used, since vector types print the internal type | 370 | /// This one should not be used, since vector types print the internal type |
| 255 | -template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | 371 | +template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy> |
| 256 | constexpr const char *type_name() { | 372 | constexpr const char *type_name() { |
| 257 | return "VECTOR"; | 373 | return "VECTOR"; |
| 258 | } | 374 | } |
| 259 | /// Print name for enumeration types | 375 | /// Print name for enumeration types |
| 260 | -template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> | 376 | +template <typename T, enable_if_t<classify_object<T>::value == enumeration, detail::enabler> = detail::dummy> |
| 261 | constexpr const char *type_name() { | 377 | constexpr const char *type_name() { |
| 262 | return "ENUM"; | 378 | return "ENUM"; |
| 263 | } | 379 | } |
| 264 | 380 | ||
| 381 | +/// Print name for enumeration types | ||
| 382 | +template <typename T, enable_if_t<classify_object<T>::value == boolean_value, detail::enabler> = detail::dummy> | ||
| 383 | +constexpr const char *type_name() { | ||
| 384 | + return "BOOLEAN"; | ||
| 385 | +} | ||
| 386 | + | ||
| 265 | /// Print for all other types | 387 | /// Print for all other types |
| 266 | -template <typename T, | ||
| 267 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value && | ||
| 268 | - !std::is_enum<T>::value, | ||
| 269 | - detail::enabler> = detail::dummy> | 388 | +template <typename T, enable_if_t<classify_object<T>::value >= string_assignable, detail::enabler> = detail::dummy> |
| 270 | constexpr const char *type_name() { | 389 | constexpr const char *type_name() { |
| 271 | return "TEXT"; | 390 | return "TEXT"; |
| 272 | } | 391 | } |
| @@ -286,6 +405,9 @@ inline int64_t to_flag_value(std::string val) { | @@ -286,6 +405,9 @@ inline int64_t to_flag_value(std::string val) { | ||
| 286 | val = detail::to_lower(val); | 405 | val = detail::to_lower(val); |
| 287 | int64_t ret; | 406 | int64_t ret; |
| 288 | if(val.size() == 1) { | 407 | if(val.size() == 1) { |
| 408 | + if(val[0] >= '1' && val[0] <= '9') { | ||
| 409 | + return (static_cast<int64_t>(val[0]) - '0'); | ||
| 410 | + } | ||
| 289 | switch(val[0]) { | 411 | switch(val[0]) { |
| 290 | case '0': | 412 | case '0': |
| 291 | case 'f': | 413 | case 'f': |
| @@ -293,22 +415,11 @@ inline int64_t to_flag_value(std::string val) { | @@ -293,22 +415,11 @@ inline int64_t to_flag_value(std::string val) { | ||
| 293 | case '-': | 415 | case '-': |
| 294 | ret = -1; | 416 | ret = -1; |
| 295 | break; | 417 | break; |
| 296 | - case '1': | ||
| 297 | case 't': | 418 | case 't': |
| 298 | case 'y': | 419 | case 'y': |
| 299 | case '+': | 420 | case '+': |
| 300 | ret = 1; | 421 | ret = 1; |
| 301 | break; | 422 | break; |
| 302 | - case '2': | ||
| 303 | - case '3': | ||
| 304 | - case '4': | ||
| 305 | - case '5': | ||
| 306 | - case '6': | ||
| 307 | - case '7': | ||
| 308 | - case '8': | ||
| 309 | - case '9': | ||
| 310 | - ret = val[0] - '0'; | ||
| 311 | - break; | ||
| 312 | default: | 423 | default: |
| 313 | throw std::invalid_argument("unrecognized character"); | 424 | throw std::invalid_argument("unrecognized character"); |
| 314 | } | 425 | } |
| @@ -325,10 +436,7 @@ inline int64_t to_flag_value(std::string val) { | @@ -325,10 +436,7 @@ inline int64_t to_flag_value(std::string val) { | ||
| 325 | } | 436 | } |
| 326 | 437 | ||
| 327 | /// Signed integers | 438 | /// Signed integers |
| 328 | -template < | ||
| 329 | - typename T, | ||
| 330 | - enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value, | ||
| 331 | - detail::enabler> = detail::dummy> | 439 | +template <typename T, enable_if_t<classify_object<T>::value == integral_value, detail::enabler> = detail::dummy> |
| 332 | bool lexical_cast(const std::string &input, T &output) { | 440 | bool lexical_cast(const std::string &input, T &output) { |
| 333 | try { | 441 | try { |
| 334 | size_t n = 0; | 442 | size_t n = 0; |
| @@ -343,9 +451,7 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -343,9 +451,7 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 343 | } | 451 | } |
| 344 | 452 | ||
| 345 | /// Unsigned integers | 453 | /// Unsigned integers |
| 346 | -template <typename T, | ||
| 347 | - enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> = | ||
| 348 | - detail::dummy> | 454 | +template <typename T, enable_if_t<classify_object<T>::value == unsigned_integral, detail::enabler> = detail::dummy> |
| 349 | bool lexical_cast(const std::string &input, T &output) { | 455 | bool lexical_cast(const std::string &input, T &output) { |
| 350 | if(!input.empty() && input.front() == '-') | 456 | if(!input.empty() && input.front() == '-') |
| 351 | return false; // std::stoull happily converts negative values to junk without any errors. | 457 | return false; // std::stoull happily converts negative values to junk without any errors. |
| @@ -363,7 +469,7 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -363,7 +469,7 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 363 | } | 469 | } |
| 364 | 470 | ||
| 365 | /// Boolean values | 471 | /// Boolean values |
| 366 | -template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> | 472 | +template <typename T, enable_if_t<classify_object<T>::value == boolean_value, detail::enabler> = detail::dummy> |
| 367 | bool lexical_cast(const std::string &input, T &output) { | 473 | bool lexical_cast(const std::string &input, T &output) { |
| 368 | try { | 474 | try { |
| 369 | auto out = to_flag_value(input); | 475 | auto out = to_flag_value(input); |
| @@ -371,11 +477,16 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -371,11 +477,16 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 371 | return true; | 477 | return true; |
| 372 | } catch(const std::invalid_argument &) { | 478 | } catch(const std::invalid_argument &) { |
| 373 | return false; | 479 | return false; |
| 480 | + } catch(const std::out_of_range &) { | ||
| 481 | + // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still | ||
| 482 | + // valid all we care about the sign | ||
| 483 | + output = (input[0] != '-'); | ||
| 484 | + return true; | ||
| 374 | } | 485 | } |
| 375 | } | 486 | } |
| 376 | 487 | ||
| 377 | /// Floats | 488 | /// Floats |
| 378 | -template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> | 489 | +template <typename T, enable_if_t<classify_object<T>::value == floating_point, detail::enabler> = detail::dummy> |
| 379 | bool lexical_cast(const std::string &input, T &output) { | 490 | bool lexical_cast(const std::string &input, T &output) { |
| 380 | try { | 491 | try { |
| 381 | size_t n = 0; | 492 | size_t n = 0; |
| @@ -389,27 +500,21 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -389,27 +500,21 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 389 | } | 500 | } |
| 390 | 501 | ||
| 391 | /// String and similar direct assignment | 502 | /// String and similar direct assignment |
| 392 | -template <typename T, | ||
| 393 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 394 | - std::is_assignable<T &, std::string>::value, | ||
| 395 | - detail::enabler> = detail::dummy> | 503 | +template <typename T, enable_if_t<classify_object<T>::value == string_assignable, detail::enabler> = detail::dummy> |
| 396 | bool lexical_cast(const std::string &input, T &output) { | 504 | bool lexical_cast(const std::string &input, T &output) { |
| 397 | output = input; | 505 | output = input; |
| 398 | return true; | 506 | return true; |
| 399 | } | 507 | } |
| 400 | 508 | ||
| 401 | /// String and similar constructible and copy assignment | 509 | /// String and similar constructible and copy assignment |
| 402 | -template <typename T, | ||
| 403 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 404 | - !std::is_assignable<T &, std::string>::value && std::is_constructible<T, std::string>::value, | ||
| 405 | - detail::enabler> = detail::dummy> | 510 | +template <typename T, enable_if_t<classify_object<T>::value == string_constructible, detail::enabler> = detail::dummy> |
| 406 | bool lexical_cast(const std::string &input, T &output) { | 511 | bool lexical_cast(const std::string &input, T &output) { |
| 407 | output = T(input); | 512 | output = T(input); |
| 408 | return true; | 513 | return true; |
| 409 | } | 514 | } |
| 410 | 515 | ||
| 411 | /// Enumerations | 516 | /// Enumerations |
| 412 | -template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> | 517 | +template <typename T, enable_if_t<classify_object<T>::value == enumeration, detail::enabler> = detail::dummy> |
| 413 | bool lexical_cast(const std::string &input, T &output) { | 518 | bool lexical_cast(const std::string &input, T &output) { |
| 414 | typename std::underlying_type<T>::type val; | 519 | typename std::underlying_type<T>::type val; |
| 415 | bool retval = detail::lexical_cast(input, val); | 520 | bool retval = detail::lexical_cast(input, val); |
| @@ -421,12 +526,7 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -421,12 +526,7 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 421 | } | 526 | } |
| 422 | 527 | ||
| 423 | /// Assignable from double or int | 528 | /// Assignable from double or int |
| 424 | -template <typename T, | ||
| 425 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 426 | - !std::is_assignable<T &, std::string>::value && | ||
| 427 | - !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value && | ||
| 428 | - is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value, | ||
| 429 | - detail::enabler> = detail::dummy> | 529 | +template <typename T, enable_if_t<classify_object<T>::value == number_constructible, detail::enabler> = detail::dummy> |
| 430 | bool lexical_cast(const std::string &input, T &output) { | 530 | bool lexical_cast(const std::string &input, T &output) { |
| 431 | int val; | 531 | int val; |
| 432 | if(lexical_cast(input, val)) { | 532 | if(lexical_cast(input, val)) { |
| @@ -442,13 +542,8 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -442,13 +542,8 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 442 | return from_stream(input, output); | 542 | return from_stream(input, output); |
| 443 | } | 543 | } |
| 444 | 544 | ||
| 445 | -/// Assignable from int64 | ||
| 446 | -template <typename T, | ||
| 447 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 448 | - !std::is_assignable<T &, std::string>::value && | ||
| 449 | - !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value && | ||
| 450 | - !is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value, | ||
| 451 | - detail::enabler> = detail::dummy> | 545 | +/// Assignable from int |
| 546 | +template <typename T, enable_if_t<classify_object<T>::value == integer_constructible, detail::enabler> = detail::dummy> | ||
| 452 | bool lexical_cast(const std::string &input, T &output) { | 547 | bool lexical_cast(const std::string &input, T &output) { |
| 453 | int val; | 548 | int val; |
| 454 | if(lexical_cast(input, val)) { | 549 | if(lexical_cast(input, val)) { |
| @@ -459,12 +554,7 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -459,12 +554,7 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 459 | } | 554 | } |
| 460 | 555 | ||
| 461 | /// Assignable from double | 556 | /// Assignable from double |
| 462 | -template <typename T, | ||
| 463 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 464 | - !std::is_assignable<T &, std::string>::value && | ||
| 465 | - !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value && | ||
| 466 | - is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value, | ||
| 467 | - detail::enabler> = detail::dummy> | 557 | +template <typename T, enable_if_t<classify_object<T>::value == double_constructible, detail::enabler> = detail::dummy> |
| 468 | bool lexical_cast(const std::string &input, T &output) { | 558 | bool lexical_cast(const std::string &input, T &output) { |
| 469 | double val; | 559 | double val; |
| 470 | if(lexical_cast(input, val)) { | 560 | if(lexical_cast(input, val)) { |
| @@ -475,15 +565,10 @@ bool lexical_cast(const std::string &input, T &output) { | @@ -475,15 +565,10 @@ bool lexical_cast(const std::string &input, T &output) { | ||
| 475 | } | 565 | } |
| 476 | 566 | ||
| 477 | /// Non-string parsable by a stream | 567 | /// Non-string parsable by a stream |
| 478 | -template <typename T, | ||
| 479 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | ||
| 480 | - !std::is_assignable<T &, std::string>::value && | ||
| 481 | - !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value && | ||
| 482 | - !is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value, | ||
| 483 | - detail::enabler> = detail::dummy> | 568 | +template <typename T, enable_if_t<classify_object<T>::value == other, detail::enabler> = detail::dummy> |
| 484 | bool lexical_cast(const std::string &input, T &output) { | 569 | bool lexical_cast(const std::string &input, T &output) { |
| 485 | static_assert(is_istreamable<T>::value, | 570 | static_assert(is_istreamable<T>::value, |
| 486 | - "option object type must have a lexical cast overload or streaming input operator(>>) defined if it " | 571 | + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " |
| 487 | "is convertible from another type use the add_option<T, XC>(...) with XC being the known type"); | 572 | "is convertible from another type use the add_option<T, XC>(...) with XC being the known type"); |
| 488 | return from_stream(input, output); | 573 | return from_stream(input, output); |
| 489 | } | 574 | } |
tests/AppTest.cpp
| @@ -647,6 +647,15 @@ TEST_F(TApp, BoolOption) { | @@ -647,6 +647,15 @@ TEST_F(TApp, BoolOption) { | ||
| 647 | args = {"-b", "-7"}; | 647 | args = {"-b", "-7"}; |
| 648 | run(); | 648 | run(); |
| 649 | EXPECT_FALSE(bflag); | 649 | EXPECT_FALSE(bflag); |
| 650 | + | ||
| 651 | + // cause an out of bounds error internally | ||
| 652 | + args = {"-b", "751615654161688126132138844896646748852"}; | ||
| 653 | + run(); | ||
| 654 | + EXPECT_TRUE(bflag); | ||
| 655 | + | ||
| 656 | + args = {"-b", "-751615654161688126132138844896646748852"}; | ||
| 657 | + run(); | ||
| 658 | + EXPECT_FALSE(bflag); | ||
| 650 | } | 659 | } |
| 651 | 660 | ||
| 652 | TEST_F(TApp, ShortOpts) { | 661 | TEST_F(TApp, ShortOpts) { |
tests/OptionGroupTest.cpp
| @@ -622,7 +622,7 @@ TEST_F(ManyGroups, Moving) { | @@ -622,7 +622,7 @@ TEST_F(ManyGroups, Moving) { | ||
| 622 | } | 622 | } |
| 623 | 623 | ||
| 624 | struct ManyGroupsPreTrigger : public ManyGroups { | 624 | struct ManyGroupsPreTrigger : public ManyGroups { |
| 625 | - size_t triggerMain, trigger1{87u}, trigger2{34u}, trigger3{27u}; | 625 | + size_t triggerMain{0u}, trigger1{87u}, trigger2{34u}, trigger3{27u}; |
| 626 | ManyGroupsPreTrigger() { | 626 | ManyGroupsPreTrigger() { |
| 627 | remove_required(); | 627 | remove_required(); |
| 628 | app.preparse_callback([this](size_t count) { triggerMain = count; }); | 628 | app.preparse_callback([this](size_t count) { triggerMain = count; }); |