Commit f7d26f26b2ca3e150d359d00bfde9b248bd3a7b8

Authored by Philip Top
Committed by GitHub
1 parent 95e7f81d

feat: counting flags (#709)

* add a counting flag to address and issue with optional<bool>  and make the flags more consistent

* move the add_flag to a single operation and add a Sum multi option policy

* style: pre-commit.ci fixes

* remove sum_flag_vector overloads

* style: pre-commit.ci fixes

* add limits include

* style: pre-commit.ci fixes

* fix some other warnings

* update docs describing the multi_option_policy

* Apply suggestions from code review

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
README.md
@@ -242,7 +242,7 @@ While all options internally are the same type, there are several ways to add an @@ -242,7 +242,7 @@ While all options internally are the same type, there are several ways to add an
242 app.add_option(option_name, help_str="") 242 app.add_option(option_name, help_str="")
243 243
244 app.add_option(option_name, 244 app.add_option(option_name,
245 - variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectorof any other supported type. 245 + variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectors of any other supported type.
246 help_string="") 246 help_string="")
247 247
248 app.add_option_function<type>(option_name, 248 app.add_option_function<type>(option_name,
@@ -363,7 +363,7 @@ Before parsing, you can set the following options: @@ -363,7 +363,7 @@ Before parsing, you can set the following options:
363 * `->allow_extra_args(true/false)`: If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option. Default value depends on the nature of the type use, containers default to true, others default to false. 363 * `->allow_extra_args(true/false)`: If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option. Default value depends on the nature of the type use, containers default to true, others default to false.
364 * `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value. 364 * `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value.
365 * `->description(str)`: Set/change the description. 365 * `->description(str)`: Set/change the description.
366 -* `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). `->join(delim)` can also be used to join with a specific delimiter. This equivalent to calling `->delimiter(delim)` and `->join()` 366 +* `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). `->join(delim)` can also be used to join with a specific delimiter. This equivalent to calling `->delimiter(delim)` and `->join()`. Valid values are `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::TakeLast`, `CLI::MultiOptionPolicy::TakeFirst`, `CLI::MultiOptionPolicy::Join`, `CLI::MultiOptionPolicy::TakeAll`, and `CLI::MultiOptionPolicy::Sum` 🚧.
367 * `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails 367 * `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails
368 * `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones. 368 * `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones.
369 * `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. 369 * `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
book/chapters/flags.md
@@ -15,7 +15,7 @@ This will bind the flag `-f` to the boolean `my_flag`. After the parsing step, ` @@ -15,7 +15,7 @@ This will bind the flag `-f` to the boolean `my_flag`. After the parsing step, `
15 15
16 ## Integer flags 16 ## Integer flags
17 17
18 -If you want to allow multiple flags, simply use any integer-like instead of a bool: 18 +If you want to allow multiple flags and count their value, simply use any integral variables instead of a bool:
19 19
20 ```cpp 20 ```cpp
21 int my_flag{0}; 21 int my_flag{0};
@@ -24,6 +24,8 @@ app.add_flag(&quot;-f&quot;, my_flag, &quot;Optional description&quot;); @@ -24,6 +24,8 @@ app.add_flag(&quot;-f&quot;, my_flag, &quot;Optional description&quot;);
24 24
25 After the parsing step, `my_flag` will contain the number of times this flag was found on the command line, including 0 if not found. 25 After the parsing step, `my_flag` will contain the number of times this flag was found on the command line, including 0 if not found.
26 26
  27 +This behavior can also be controlled manually via `->multi_option_policy(CLI::MultiOptionPolicy::Sum)` as of version 2.2.
  28 +
27 ## Arbitrary type flags 29 ## Arbitrary type flags
28 30
29 CLI11 allows the type of the variable to assign to in the `add_flag` function to be any supported type. This is particularly useful in combination with specifying default values for flags. The allowed types include bool, int, float, vector, enum, or string-like. 31 CLI11 allows the type of the variable to assign to in the `add_flag` function to be any supported type. This is particularly useful in combination with specifying default values for flags. The allowed types include bool, int, float, vector, enum, or string-like.
book/chapters/options.md
@@ -171,7 +171,7 @@ When you call `add_option`, you get a pointer to the added option. You can use t @@ -171,7 +171,7 @@ When you call `add_option`, you get a pointer to the added option. You can use t
171 | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. | 171 | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. |
172 | `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` | 172 | `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` |
173 | `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' | 173 | `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' |
174 -| `->multi_option_policy( CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next four lines for shortcuts to set this more easily. | 174 +| `->multi_option_policy( CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, `Join`, and `Sum` are also available. See the next four lines for shortcuts to set this more easily. |
175 | `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. | 175 | `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. |
176 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | 176 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
177 | `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` | 177 | `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` |
@@ -211,7 +211,7 @@ One of CLI11&#39;s systems to allow customizability without high levels of verbosity @@ -211,7 +211,7 @@ One of CLI11&#39;s systems to allow customizability without high levels of verbosity
211 211
212 * `group`: The group name starts as "Options" 212 * `group`: The group name starts as "Options"
213 * `required`: If the option must be given. Defaults to `false`. Is ignored for flags. 213 * `required`: If the option must be given. Defaults to `false`. Is ignored for flags.
214 -* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag. 214 +* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` or `CLI::MultiOptionPolicy::Sum` regardless of the default, so that multiple bool flags does not cause an error. But you can override that setting by calling the `multi_option_policy` directly.
215 * `ignore_case`: Allow any mixture of cases for the option or flag name 215 * `ignore_case`: Allow any mixture of cases for the option or flag name
216 * `ignore_underscore`: Allow any number of underscores in the option or flag name 216 * `ignore_underscore`: Allow any number of underscores in the option or flag name
217 * `configurable`: Specify whether an option can be configured through a config file 217 * `configurable`: Specify whether an option can be configured through a config file
include/CLI/App.hpp
@@ -61,6 +61,22 @@ class App; @@ -61,6 +61,22 @@ class App;
61 61
62 using App_p = std::shared_ptr<App>; 62 using App_p = std::shared_ptr<App>;
63 63
  64 +namespace detail {
  65 +/// helper functions for adding in appropriate flag modifiers for add_flag
  66 +
  67 +template <typename T, enable_if_t<!std::is_integral<T>::value || (sizeof(T) <= 1U), detail::enabler> = detail::dummy>
  68 +Option *default_flag_modifiers(Option *opt) {
  69 + return opt->always_capture_default();
  70 +}
  71 +
  72 +/// summing modifiers
  73 +template <typename T, enable_if_t<std::is_integral<T>::value && (sizeof(T) > 1U), detail::enabler> = detail::dummy>
  74 +Option *default_flag_modifiers(Option *opt) {
  75 + return opt->multi_option_policy(MultiOptionPolicy::Sum)->default_str("0")->force_callback();
  76 +}
  77 +
  78 +} // namespace detail
  79 +
64 class Option_group; 80 class Option_group;
65 /// Creates a command line program, with very few defaults. 81 /// Creates a command line program, with very few defaults.
66 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated 82 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
@@ -807,43 +823,21 @@ class App { @@ -807,43 +823,21 @@ class App {
807 return _add_flag_internal(flag_name, CLI::callback_t(), flag_description); 823 return _add_flag_internal(flag_name, CLI::callback_t(), flag_description);
808 } 824 }
809 825
810 - /// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one  
811 - /// if `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.  
812 - template <  
813 - typename T,  
814 - enable_if_t<std::is_constructible<T, std::int64_t>::value && !std::is_const<T>::value && !is_bool<T>::value,  
815 - detail::enabler> = detail::dummy>  
816 - Option *add_flag(std::string flag_name,  
817 - T &flag_count, ///< A variable holding the count  
818 - std::string flag_description = "") {  
819 - flag_count = 0;  
820 - CLI::callback_t fun = [&flag_count](const CLI::results_t &res) {  
821 - try {  
822 - detail::sum_flag_vector(res, flag_count);  
823 - } catch(const std::invalid_argument &) {  
824 - return false;  
825 - }  
826 - return true;  
827 - };  
828 - return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))  
829 - ->multi_option_policy(MultiOptionPolicy::TakeAll);  
830 - }  
831 -  
832 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes 826 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
833 /// that can be converted from a string 827 /// that can be converted from a string
834 template <typename T, 828 template <typename T,
835 enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value && 829 enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
836 - (!std::is_constructible<T, std::int64_t>::value || is_bool<T>::value) &&  
837 !std::is_constructible<std::function<void(int)>, T>::value, 830 !std::is_constructible<std::function<void(int)>, T>::value,
838 detail::enabler> = detail::dummy> 831 detail::enabler> = detail::dummy>
839 Option *add_flag(std::string flag_name, 832 Option *add_flag(std::string flag_name,
840 - T &flag_result, ///< A variable holding true if passed 833 + T &flag_result, ///< A variable holding the flag result
841 std::string flag_description = "") { 834 std::string flag_description = "") {
842 835
843 CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { 836 CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
844 return CLI::detail::lexical_cast(res[0], flag_result); 837 return CLI::detail::lexical_cast(res[0], flag_result);
845 }; 838 };
846 - return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))->run_callback_for_default(); 839 + auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
  840 + return detail::default_flag_modifiers<T>(opt);
847 } 841 }
848 842
849 /// Vector version to capture multiple flags. 843 /// Vector version to capture multiple flags.
@@ -888,13 +882,13 @@ class App { @@ -888,13 +882,13 @@ class App {
888 std::string flag_description = "") { 882 std::string flag_description = "") {
889 883
890 CLI::callback_t fun = [function](const CLI::results_t &res) { 884 CLI::callback_t fun = [function](const CLI::results_t &res) {
891 - std::int64_t flag_count = 0;  
892 - detail::sum_flag_vector(res, flag_count); 885 + std::int64_t flag_count{0};
  886 + CLI::detail::lexical_cast(res[0], flag_count);
893 function(flag_count); 887 function(flag_count);
894 return true; 888 return true;
895 }; 889 };
896 return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)) 890 return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
897 - ->multi_option_policy(MultiOptionPolicy::TakeAll); 891 + ->multi_option_policy(MultiOptionPolicy::Sum);
898 } 892 }
899 893
900 #ifdef CLI11_CPP14 894 #ifdef CLI11_CPP14
include/CLI/Option.hpp
@@ -40,7 +40,8 @@ enum class MultiOptionPolicy : char { @@ -40,7 +40,8 @@ enum class MultiOptionPolicy : char {
40 TakeLast, //!< take only the last Expected number of arguments 40 TakeLast, //!< take only the last Expected number of arguments
41 TakeFirst, //!< take only the first Expected number of arguments 41 TakeFirst, //!< take only the first Expected number of arguments
42 Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') 42 Join, //!< merge all the arguments together into a single string via the delimiter character default('\n')
43 - TakeAll //!< just get all the passed argument regardless 43 + TakeAll, //!< just get all the passed argument regardless
  44 + Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter
44 }; 45 };
45 46
46 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way 47 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way
@@ -1266,6 +1267,9 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1266,6 +1267,9 @@ class Option : public OptionBase&lt;Option&gt; {
1266 res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_))); 1267 res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
1267 } 1268 }
1268 break; 1269 break;
  1270 + case MultiOptionPolicy::Sum:
  1271 + res.push_back(detail::sum_string_vector(original));
  1272 + break;
1269 case MultiOptionPolicy::Throw: 1273 case MultiOptionPolicy::Throw:
1270 default: { 1274 default: {
1271 auto num_min = static_cast<std::size_t>(get_items_expected_min()); 1275 auto num_min = static_cast<std::size_t>(get_items_expected_min());
@@ -1352,7 +1356,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1352,7 +1356,7 @@ class Option : public OptionBase&lt;Option&gt; {
1352 } 1356 }
1353 return result_count; 1357 return result_count;
1354 } 1358 }
1355 -}; // namespace CLI 1359 +};
1356 1360
1357 // [CLI11:option_hpp:end] 1361 // [CLI11:option_hpp:end]
1358 } // namespace CLI 1362 } // namespace CLI
include/CLI/TypeTools.hpp
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 // [CLI11:public_includes:set] 9 // [CLI11:public_includes:set]
10 #include <cstdint> 10 #include <cstdint>
11 #include <exception> 11 #include <exception>
  12 +#include <limits>
12 #include <memory> 13 #include <memory>
13 #include <string> 14 #include <string>
14 #include <type_traits> 15 #include <type_traits>
@@ -799,7 +800,16 @@ bool integral_conversion(const std::string &amp;input, T &amp;output) noexcept { @@ -799,7 +800,16 @@ bool integral_conversion(const std::string &amp;input, T &amp;output) noexcept {
799 char *val = nullptr; 800 char *val = nullptr;
800 std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); 801 std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
801 output = static_cast<T>(output_ll); 802 output = static_cast<T>(output_ll);
802 - return val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll; 803 + if(val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll) {
  804 + return true;
  805 + }
  806 + val = nullptr;
  807 + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0);
  808 + if(val == (input.c_str() + input.size())) {
  809 + output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll);
  810 + return (static_cast<std::int64_t>(output) == output_sll);
  811 + }
  812 + return false;
803 } 813 }
804 814
805 /// Convert to a signed integral 815 /// Convert to a signed integral
@@ -811,7 +821,15 @@ bool integral_conversion(const std::string &amp;input, T &amp;output) noexcept { @@ -811,7 +821,15 @@ bool integral_conversion(const std::string &amp;input, T &amp;output) noexcept {
811 char *val = nullptr; 821 char *val = nullptr;
812 std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); 822 std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0);
813 output = static_cast<T>(output_ll); 823 output = static_cast<T>(output_ll);
814 - return val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll; 824 + if(val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll) {
  825 + return true;
  826 + }
  827 + if(input == "true") {
  828 + // this is to deal with a few oddities with flags and wrapper int types
  829 + output = static_cast<T>(1);
  830 + return true;
  831 + }
  832 + return false;
815 } 833 }
816 834
817 /// Convert a flag into an integer value typically binary flags 835 /// Convert a flag into an integer value typically binary flags
@@ -1501,61 +1519,40 @@ bool lexical_conversion(const std::vector&lt;std::string&gt; &amp;strings, AssignTo &amp;outpu @@ -1501,61 +1519,40 @@ bool lexical_conversion(const std::vector&lt;std::string&gt; &amp;strings, AssignTo &amp;outpu
1501 return false; 1519 return false;
1502 } 1520 }
1503 1521
1504 -/// Sum a vector of flag representations  
1505 -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is  
1506 -/// by  
1507 -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most  
1508 -/// common true and false strings then uses stoll to convert the rest for summing  
1509 -template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy>  
1510 -void sum_flag_vector(const std::vector<std::string> &flags, T &output) {  
1511 - std::int64_t count{0};  
1512 - for(auto &flag : flags) {  
1513 - count += detail::to_flag_value(flag);  
1514 - }  
1515 - output = (count > 0) ? static_cast<T>(count) : T{0};  
1516 -}  
1517 -  
1518 -/// Sum a vector of flag representations  
1519 -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is  
1520 -/// by  
1521 -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most  
1522 -/// common true and false strings then uses stoll to convert the rest for summing  
1523 -template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy>  
1524 -void sum_flag_vector(const std::vector<std::string> &flags, T &output) {  
1525 - std::int64_t count{0};  
1526 - for(auto &flag : flags) {  
1527 - count += detail::to_flag_value(flag); 1522 +/// Sum a vector of strings
  1523 +inline std::string sum_string_vector(const std::vector<std::string> &values) {
  1524 + double val{0.0};
  1525 + bool fail{false};
  1526 + std::string output;
  1527 + for(const auto &arg : values) {
  1528 + double tv{0.0};
  1529 + auto comp = detail::lexical_cast<double>(arg, tv);
  1530 + if(!comp) {
  1531 + try {
  1532 + tv = static_cast<double>(detail::to_flag_value(arg));
  1533 + } catch(const std::exception &) {
  1534 + fail = true;
  1535 + break;
  1536 + }
  1537 + }
  1538 + val += tv;
1528 } 1539 }
1529 - output = static_cast<T>(count);  
1530 -}  
1531 -  
1532 -#ifdef _MSC_VER  
1533 -#pragma warning(push)  
1534 -#pragma warning(disable : 4800)  
1535 -#endif  
1536 -// with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style so will  
1537 -// most likely still work  
1538 -  
1539 -/// Sum a vector of flag representations  
1540 -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is  
1541 -/// by  
1542 -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most  
1543 -/// common true and false strings then uses stoll to convert the rest for summing  
1544 -template <typename T,  
1545 - enable_if_t<!std::is_signed<T>::value && !std::is_unsigned<T>::value, detail::enabler> = detail::dummy>  
1546 -void sum_flag_vector(const std::vector<std::string> &flags, T &output) {  
1547 - std::int64_t count{0};  
1548 - for(auto &flag : flags) {  
1549 - count += detail::to_flag_value(flag); 1540 + if(fail) {
  1541 + for(const auto &arg : values) {
  1542 + output.append(arg);
  1543 + }
  1544 + } else {
  1545 + if(val <= static_cast<double>(std::numeric_limits<std::int64_t>::min()) ||
  1546 + val >= static_cast<double>(std::numeric_limits<std::int64_t>::max()) ||
  1547 + val == static_cast<std::int64_t>(val)) {
  1548 + output = detail::value_string(static_cast<int64_t>(val));
  1549 + } else {
  1550 + output = detail::value_string(val);
  1551 + }
1550 } 1552 }
1551 - std::string out = detail::to_string(count);  
1552 - lexical_cast(out, output); 1553 + return output;
1553 } 1554 }
1554 1555
1555 -#ifdef _MSC_VER  
1556 -#pragma warning(pop)  
1557 -#endif  
1558 -  
1559 } // namespace detail 1556 } // namespace detail
1560 // [CLI11:type_tools_hpp:end] 1557 // [CLI11:type_tools_hpp:end]
1561 } // namespace CLI 1558 } // namespace CLI
tests/AppTest.cpp
@@ -212,7 +212,7 @@ TEST_CASE_METHOD(TApp, &quot;OneFlagRefValueFalse&quot;, &quot;[app]&quot;) { @@ -212,7 +212,7 @@ TEST_CASE_METHOD(TApp, &quot;OneFlagRefValueFalse&quot;, &quot;[app]&quot;) {
212 run(); 212 run();
213 CHECK(app.count("-c") == 1u); 213 CHECK(app.count("-c") == 1u);
214 CHECK(app.count("--count") == 1u); 214 CHECK(app.count("--count") == 1u);
215 - CHECK(ref == -1); 215 + CHECK(ref == 0);
216 216
217 args = {"--count=happy"}; 217 args = {"--count=happy"};
218 CHECK_THROWS_AS(run(), CLI::ConversionError); 218 CHECK_THROWS_AS(run(), CLI::ConversionError);
@@ -774,6 +774,42 @@ TEST_CASE_METHOD(TApp, &quot;JoinOpt&quot;, &quot;[app]&quot;) { @@ -774,6 +774,42 @@ TEST_CASE_METHOD(TApp, &quot;JoinOpt&quot;, &quot;[app]&quot;) {
774 CHECK("one\ntwo" == str); 774 CHECK("one\ntwo" == str);
775 } 775 }
776 776
  777 +TEST_CASE_METHOD(TApp, "SumOpt", "[app]") {
  778 +
  779 + int val;
  780 + app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
  781 +
  782 + args = {"--val=1", "--val=4"};
  783 +
  784 + run();
  785 +
  786 + CHECK(5 == val);
  787 +}
  788 +
  789 +TEST_CASE_METHOD(TApp, "SumOptFloat", "[app]") {
  790 +
  791 + double val;
  792 + app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
  793 +
  794 + args = {"--val=1.3", "--val=-0.7"};
  795 +
  796 + run();
  797 +
  798 + CHECK(0.6 == val);
  799 +}
  800 +
  801 +TEST_CASE_METHOD(TApp, "SumOptString", "[app]") {
  802 +
  803 + std::string val;
  804 + app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
  805 +
  806 + args = {"--val=i", "--val=2"};
  807 +
  808 + run();
  809 +
  810 + CHECK("i2" == val);
  811 +}
  812 +
777 TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") { 813 TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") {
778 814
779 std::string str; 815 std::string str;
tests/ConfigFileTest.cpp
@@ -2254,7 +2254,7 @@ TEST_CASE_METHOD(TApp, &quot;TomlOutputFlag&quot;, &quot;[config]&quot;) { @@ -2254,7 +2254,7 @@ TEST_CASE_METHOD(TApp, &quot;TomlOutputFlag&quot;, &quot;[config]&quot;) {
2254 CHECK_THAT(str, Contains("simple=3")); 2254 CHECK_THAT(str, Contains("simple=3"));
2255 CHECK_THAT(str, !Contains("nothing")); 2255 CHECK_THAT(str, !Contains("nothing"));
2256 CHECK_THAT(str, Contains("onething=true")); 2256 CHECK_THAT(str, Contains("onething=true"));
2257 - CHECK_THAT(str, Contains("something=[true, true]")); 2257 + CHECK_THAT(str, Contains("something=2"));
2258 2258
2259 str = app.config_to_str(true); 2259 str = app.config_to_str(true);
2260 CHECK_THAT(str, Contains("nothing")); 2260 CHECK_THAT(str, Contains("nothing"));
@@ -2685,7 +2685,7 @@ TEST_CASE_METHOD(TApp, &quot;IniOutputFlag&quot;, &quot;[config]&quot;) { @@ -2685,7 +2685,7 @@ TEST_CASE_METHOD(TApp, &quot;IniOutputFlag&quot;, &quot;[config]&quot;) {
2685 CHECK_THAT(str, Contains("simple=3")); 2685 CHECK_THAT(str, Contains("simple=3"));
2686 CHECK_THAT(str, !Contains("nothing")); 2686 CHECK_THAT(str, !Contains("nothing"));
2687 CHECK_THAT(str, Contains("onething=true")); 2687 CHECK_THAT(str, Contains("onething=true"));
2688 - CHECK_THAT(str, Contains("something=true true")); 2688 + CHECK_THAT(str, Contains("something=2"));
2689 2689
2690 str = app.config_to_str(true); 2690 str = app.config_to_str(true);
2691 CHECK_THAT(str, Contains("nothing")); 2691 CHECK_THAT(str, Contains("nothing"));
tests/OptionTypeTest.cpp
@@ -146,7 +146,7 @@ TEST_CASE_METHOD(TApp, &quot;atomic_bool_flags&quot;, &quot;[optiontype]&quot;) { @@ -146,7 +146,7 @@ TEST_CASE_METHOD(TApp, &quot;atomic_bool_flags&quot;, &quot;[optiontype]&quot;) {
146 std::atomic<int> iflag{0}; 146 std::atomic<int> iflag{0};
147 147
148 app.add_flag("-b", bflag); 148 app.add_flag("-b", bflag);
149 - app.add_flag("-i,--int", iflag); 149 + app.add_flag("-i,--int", iflag)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
150 150
151 args = {"-b", "-i"}; 151 args = {"-b", "-i"};
152 run(); 152 run();
tests/OptionalTest.cpp
@@ -130,6 +130,27 @@ TEST_CASE_METHOD(TApp, &quot;StdOptionalUint&quot;, &quot;[optional]&quot;) { @@ -130,6 +130,27 @@ TEST_CASE_METHOD(TApp, &quot;StdOptionalUint&quot;, &quot;[optional]&quot;) {
130 CLI::detail::object_category::wrapper_value); 130 CLI::detail::object_category::wrapper_value);
131 } 131 }
132 132
  133 +TEST_CASE_METHOD(TApp, "StdOptionalbool", "[optional]") {
  134 + std::optional<bool> opt{};
  135 + CHECK(!opt);
  136 + app.add_flag("--opt,!--no-opt", opt);
  137 + CHECK(!opt);
  138 + run();
  139 + CHECK(!opt);
  140 +
  141 + args = {"--opt"};
  142 + run();
  143 + CHECK(opt);
  144 + CHECK(*opt);
  145 +
  146 + args = {"--no-opt"};
  147 + run();
  148 + CHECK(opt);
  149 + CHECK_FALSE(*opt);
  150 + static_assert(CLI::detail::classify_object<std::optional<bool>>::value ==
  151 + CLI::detail::object_category::wrapper_value);
  152 +}
  153 +
133 #ifdef _MSC_VER 154 #ifdef _MSC_VER
134 #pragma warning(default : 4244) 155 #pragma warning(default : 4244)
135 #endif 156 #endif