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 242 app.add_option(option_name, help_str="")
243 243  
244 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 246 help_string="")
247 247  
248 248 app.add_option_function<type>(option_name,
... ... @@ -363,7 +363,7 @@ Before parsing, you can set the following options:
363 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 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 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 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 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 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 15  
16 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 20 ```cpp
21 21 int my_flag{0};
... ... @@ -24,6 +24,8 @@ app.add_flag(&quot;-f&quot;, my_flag, &quot;Optional description&quot;);
24 24  
25 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 29 ## Arbitrary type flags
28 30  
29 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 171 | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. |
172 172 | `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` |
173 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 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 176 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
177 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 211  
212 212 * `group`: The group name starts as "Options"
213 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 215 * `ignore_case`: Allow any mixture of cases for the option or flag name
216 216 * `ignore_underscore`: Allow any number of underscores in the option or flag name
217 217 * `configurable`: Specify whether an option can be configured through a config file
... ...
include/CLI/App.hpp
... ... @@ -61,6 +61,22 @@ class App;
61 61  
62 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 80 class Option_group;
65 81 /// Creates a command line program, with very few defaults.
66 82 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
... ... @@ -807,43 +823,21 @@ class App {
807 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 826 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
833 827 /// that can be converted from a string
834 828 template <typename T,
835 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 830 !std::is_constructible<std::function<void(int)>, T>::value,
838 831 detail::enabler> = detail::dummy>
839 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 834 std::string flag_description = "") {
842 835  
843 836 CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
844 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 843 /// Vector version to capture multiple flags.
... ... @@ -888,13 +882,13 @@ class App {
888 882 std::string flag_description = "") {
889 883  
890 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 887 function(flag_count);
894 888 return true;
895 889 };
896 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 894 #ifdef CLI11_CPP14
... ...
include/CLI/Option.hpp
... ... @@ -40,7 +40,8 @@ enum class MultiOptionPolicy : char {
40 40 TakeLast, //!< take only the last Expected number of arguments
41 41 TakeFirst, //!< take only the first Expected number of arguments
42 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 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 1267 res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
1267 1268 }
1268 1269 break;
  1270 + case MultiOptionPolicy::Sum:
  1271 + res.push_back(detail::sum_string_vector(original));
  1272 + break;
1269 1273 case MultiOptionPolicy::Throw:
1270 1274 default: {
1271 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 1356 }
1353 1357 return result_count;
1354 1358 }
1355   -}; // namespace CLI
  1359 +};
1356 1360  
1357 1361 // [CLI11:option_hpp:end]
1358 1362 } // namespace CLI
... ...
include/CLI/TypeTools.hpp
... ... @@ -9,6 +9,7 @@
9 9 // [CLI11:public_includes:set]
10 10 #include <cstdint>
11 11 #include <exception>
  12 +#include <limits>
12 13 #include <memory>
13 14 #include <string>
14 15 #include <type_traits>
... ... @@ -799,7 +800,16 @@ bool integral_conversion(const std::string &amp;input, T &amp;output) noexcept {
799 800 char *val = nullptr;
800 801 std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
801 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 815 /// Convert to a signed integral
... ... @@ -811,7 +821,15 @@ bool integral_conversion(const std::string &amp;input, T &amp;output) noexcept {
811 821 char *val = nullptr;
812 822 std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0);
813 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 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 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 1556 } // namespace detail
1560 1557 // [CLI11:type_tools_hpp:end]
1561 1558 } // namespace CLI
... ...
tests/AppTest.cpp
... ... @@ -212,7 +212,7 @@ TEST_CASE_METHOD(TApp, &quot;OneFlagRefValueFalse&quot;, &quot;[app]&quot;) {
212 212 run();
213 213 CHECK(app.count("-c") == 1u);
214 214 CHECK(app.count("--count") == 1u);
215   - CHECK(ref == -1);
  215 + CHECK(ref == 0);
216 216  
217 217 args = {"--count=happy"};
218 218 CHECK_THROWS_AS(run(), CLI::ConversionError);
... ... @@ -774,6 +774,42 @@ TEST_CASE_METHOD(TApp, &quot;JoinOpt&quot;, &quot;[app]&quot;) {
774 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 813 TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") {
778 814  
779 815 std::string str;
... ...
tests/ConfigFileTest.cpp
... ... @@ -2254,7 +2254,7 @@ TEST_CASE_METHOD(TApp, &quot;TomlOutputFlag&quot;, &quot;[config]&quot;) {
2254 2254 CHECK_THAT(str, Contains("simple=3"));
2255 2255 CHECK_THAT(str, !Contains("nothing"));
2256 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 2259 str = app.config_to_str(true);
2260 2260 CHECK_THAT(str, Contains("nothing"));
... ... @@ -2685,7 +2685,7 @@ TEST_CASE_METHOD(TApp, &quot;IniOutputFlag&quot;, &quot;[config]&quot;) {
2685 2685 CHECK_THAT(str, Contains("simple=3"));
2686 2686 CHECK_THAT(str, !Contains("nothing"));
2687 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 2690 str = app.config_to_str(true);
2691 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 146 std::atomic<int> iflag{0};
147 147  
148 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 151 args = {"-b", "-i"};
152 152 run();
... ...
tests/OptionalTest.cpp
... ... @@ -130,6 +130,27 @@ TEST_CASE_METHOD(TApp, &quot;StdOptionalUint&quot;, &quot;[optional]&quot;) {
130 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 154 #ifdef _MSC_VER
134 155 #pragma warning(default : 4244)
135 156 #endif
... ...