Commit 27da2f952ebc41c510d08b20142d837f6435f644

Authored by Philip Top
Committed by GitHub
1 parent f346f298

Container options (#423)

* Update options.md book chapter and the readme to better reflect current usage and the modifications to the add_options templates.

add support in add_option for wrapper types, such as std::optional, boost::optional or other types with a value_type trait.  Add support for generalized containers beyond vector,  add support for nested tuples and vectors, and complex numbers directly in add_option.  This includes several new type traits and object categories.

Upgrade the google test version to better support templated tests.

add support for vector argument separator `%%`

* update formatting to match recent changes

* Apply suggestions from code review

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
README.md
... ... @@ -195,14 +195,15 @@ While all options internally are the same type, there are several ways to add an
195 195 app.add_option(option_name, help_str="")
196 196  
197 197 app.add_option(option_name,
198   - variable_to_bind_to, // bool, int, float, vector, enum, 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 πŸ†•.
  198 + variable_to_bind_to, // bool, int, float, vector, enum, 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 vector🚧 of any other supported type.
199 199 help_string="")
200 200  
201 201 app.add_option_function<type>(option_name,
202 202 function <void(const type &value)>, // type can be any type supported by add_option
203 203 help_string="")
204 204  
205   -app.add_complex(... // Special case: support for complex numbers
  205 +app.add_complex(... // Special case: support for complex numbers ⚠️. Complex numbers are now fully supported in the add_option so this function is redundant.
  206 +
206 207 // πŸ†• There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type
207 208 app.add_option<typename T, typename XC>(option_name,
208 209 T &output, // output must be assignable or constructible from a value of type XC
... ... @@ -213,7 +214,7 @@ app.add_flag(option_name,
213 214 help_string="")
214 215  
215 216 app.add_flag(option_name,
216   - variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or any singular object with a defined conversion from a string like add_option
  217 + variable_to_bind_to, // bool, int, float, complex, containers, enum, or string-like, or any singular object with a defined conversion from a string like add_option
217 218 help_string="")
218 219  
219 220 app.add_flag_function(option_name,
... ... @@ -245,9 +246,9 @@ app.add_option&lt;vtype,std:string&gt;(&quot;--vs&quot;,v1);
245 246 app.add_option<vtype,int>("--vi",v1);
246 247 app.add_option<vtype,double>("--vf",v1);
247 248 ```
248   -otherwise the output would default to a string. The `add_option` can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.
  249 +otherwise the output would default to a string. The `add_option` can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std\::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.
249 250  
250   -Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` are supported directly, other optional types can be added using the two parameter template. See [CLI11 Advanced Topics/Custom Converters][] for information on how this could be done and how you can add your own converters for additional types.
  251 +Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` and any other wrapper types are supported directly. For purposes of CLI11 wrapper types are those which `value_type` definition. See [CLI11 Advanced Topics/Custom Converters][] for information on how you can add your own converters for additional types.
251 252  
252 253 Vector types can also be used in the two parameter template overload
253 254 ```
... ... @@ -308,7 +309,7 @@ Before parsing, you can set the following options:
308 309 - `->disable_flag_override()`: From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
309 310 - `->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.
310 311 - `->description(str)`: Set/change the description.
311   -- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, 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).
  312 +- `->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).
312 313 - `->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
313 314 - `->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.
314 315 - `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
... ... @@ -319,7 +320,7 @@ Before parsing, you can set the following options:
319 320 - `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses.
320 321 - `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
321 322 - `default_str(string)`: Set the default string directly. This string will also be used as a default value if no arguments are passed and the value is requested.
322   -- `default_val(value)`: πŸ†• Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or a stream operator).
  323 +- `default_val(value)`: πŸ†• Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator).
323 324  
324 325  
325 326 These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
... ...
book/chapters/options.md
... ... @@ -9,7 +9,7 @@ int int_option{0};
9 9 app.add_option("-i", int_option, "Optional description");
10 10 ```
11 11  
12   -This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option. If you do not add this, you do not even need your option value to be printable[^1].
  12 +This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option.
13 13  
14 14 ```cpp
15 15 int int_option{0};
... ... @@ -20,11 +20,15 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi
20 20  
21 21 | Type | CLI11 |
22 22 |-------------|-------|
23   -| int-like | Integer conversion up to 64-bit, can be unsigned |
24   -| float-like | Floating point conversions |
25   -| string-like | Anything else that can be shifted into a StringStream |
26   -| vector-like | A vector of the above three types (see below) |
  23 +| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number |
  24 +| string-like | std\::string, or anything that can be constructed from or assigned a std\::string |
  25 +| complex-number | std::complex or any type which has a real(), and imag() operations available, will allow 1 or 2 string definitions like "1+2j" or two arguments "1","2" |
  26 +| enumeration | any enum or enum class type is supported through conversion from the underlying type(typically int, though it can be specified otherwise) |
  27 +| container-like | a container(like vector) of any available types including other containers |
  28 +| wrapper | any other object with a `value_type` static definition where the type specified by `value_type` is one of type in this list |
  29 +| tuple | a tuple, pair, or array, or other type with a tuple size and tuple_type operations defined and the members being a type contained in this list |
27 30 | function | A function that takes an array of strings and returns a string that describes the conversion failure or empty for success. May be the empty function. (`{}`) |
  31 +| streamable | any other type with a `<<` operator will also work |
28 32  
29 33 By default, CLI11 will assume that an option is optional, and one value is expected if you do not use a vector. You can change this on a specific option using option modifiers.
30 34  
... ... @@ -46,15 +50,15 @@ To make a positional option, you simply give CLI11 one name that does not start
46 50  
47 51 This would make two short option aliases, two long option alias, and the option would be also be accepted as a positional.
48 52  
49   -## Vectors of options
  53 +## Containers of options
50 54  
51   -If you use a vector instead of a plain option, you can accept more than one value on the command line. By default, a vector accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restore with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options:[^2]
  55 +If you use a vector or other container instead of a plain option, you can accept more than one value on the command line. By default, a container accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restored with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options.
52 56  
53 57 | Separate names | Combined names |
54 58 |-------------------|-----------------|
55 59 | `--vec 1 --vec 2` | `--vec 1 2` |
56 60  
57   -The original version did allow the option system to access information on the grouping of options received, but was removed for simplicity.
  61 +It is also possible to specify a minimum and maximum number through `->expected(Min,Max)`. It is also possible to specify a min and max type size for the elements of the container. It most cases these values will be automatically determined but a user can manually restrict them.
58 62  
59 63 An example of setting up a vector option:
60 64  
... ... @@ -65,6 +69,43 @@ app.add_option(&quot;--vec&quot;, int_vec, &quot;My vector option&quot;);
65 69  
66 70 Vectors will be replaced by the parsed content if the option is given on the command line.
67 71  
  72 +A definition of a container for purposes of CLI11 is a type with a `end()`, `insert(...)`, `clear()` and `value_type` definitions. This includes `vector`, `set`, `deque`, `list`, `forward_iist`, `map`, `unordered_map` and a few others from the standard library, and many other containers from the boost library.
  73 +
  74 +### containers of containers
  75 +Containers of containers are also supported.
  76 +```cpp
  77 +std::vector<std::vector<int>> int_vec;
  78 +app.add_option("--vec", int_vec, "My vector of vectors option");
  79 +```
  80 +CLI11 inserts a separator sequence at the start of each argument call to separate the vectors. So unless the separators are injected as part of the command line each call of the option on the command line will result in a separate element of the outer vector. This can be manually controlled via `inject_separator(true|false)` but in nearly all cases this should be left to the defaults. To insert of a separator from the command line add a `%%` where the separation should occur.
  81 +```
  82 +cmd --vec_of_vec 1 2 3 4 %% 1 2
  83 +```
  84 +would then result in a container of size 2 with the first element containing 4 values and the second 2.
  85 +
  86 +This separator is also the only way to get values into something like
  87 +```cpp
  88 +std::pair<std::vector<int>,std::vector<int>> two_vecs;
  89 +app.add_option("--vec", two_vecs, "pair of vectors");
  90 +```
  91 +without calling the argument twice.
  92 +
  93 +Further levels of nesting containers should compile but intermediate layers will only have a single element in the container, so is probably not that useful.
  94 +
  95 +### Nested types
  96 +Types can be nested For example
  97 +```cpp
  98 +std::map<int, std::pair<int,std::string>> map;
  99 +app.add_option("--dict", map, "map of pairs");
  100 +```
  101 +
  102 +will require 3 arguments for each invocation, and multiple sets of 3 arguments can be entered for a single invocation on the command line.
  103 +
  104 +```cpp
  105 +std::map<int, std::pair<int,std::vector<std::string>>> map;
  106 +app.add_option("--dict", map, "map of pairs");
  107 +```
  108 +will result in a requirement for 2 integers on each invocation and absorb an unlimited number of strings including 0.
68 109  
69 110 ## Option modifiers
70 111  
... ... @@ -75,25 +116,32 @@ When you call `add_option`, you get a pointer to the added option. You can use t
75 116 | `->required()` | The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. |
76 117 | `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. |
77 118 | `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. |
  119 +| `->type_size(N)` | specify that each block of values would consist of N elements |
  120 +| `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements |
78 121 | `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. |
79 122 | `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. |
80 123 | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. |
81 124 | `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. |
  125 +| `->description(string)` | Set/change the description |
82 126 | `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). |
83   -| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments, new in CLI11 1.7). |
  127 +| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments). |
84 128 | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. |
85   -| `->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 three lines for shortcuts to set this more easily. |
  129 +| `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` |
  130 +| `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' |
  131 +| `->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. |
86 132 | `->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()`. |
87 133 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
  134 +| `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` |
88 135 | `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines or the specified delimiter to join all arguments into a single string output. |
89   -| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. |
90   -| `->check(CLI::ExistingFile)` | Requires that the file exists if given. |
91   -| `->check(CLI::ExistingDirectory)` | Requires that the directory exists. |
92   -| `->check(CLI::NonexistentPath)` | Requires that the path does not exist. |
93   -| `->check(CLI::Range(min,max))` | Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. |
  136 +| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. this also sets the delimiter |
  137 +| `->check(Validator)` | perform a check on the returned results to verify they meet some criteria. See [Validators](./validators.md) for more info |
  138 +| `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info |
94 139 | `->each(void(std::string))` | Run a function on each parsed value, *in order*. |
  140 +| `->default_str(string)` | set a default string for use in the help and as a default value if no arguments are passed and a value is requested |
  141 +| `->default_function(string())` | Advanced: Change the function that `capture_default_str()` uses. |
  142 +| `->default_val(value)` | Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator). |
95 143  
96   -The `->check(...)` modifiers adds a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed.
  144 +The `->check(...)` and `->transform(...)` modifiers can also take a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed.
97 145  
98 146 ## Using the `CLI::Option` pointer
99 147  
... ... @@ -110,12 +158,17 @@ if(* opt)
110 158  
111 159 ## Inheritance of defaults
112 160  
113   -One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are four settings that can be set and inherited:
  161 +One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are a number of settings that can be set and inherited:
114 162  
115 163 * `group`: The group name starts as "Options"
116 164 * `required`: If the option must be given. Defaults to `false`. Is ignored for flags.
117 165 * `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.
118 166 * `ignore_case`: Allow any mixture of cases for the option or flag name
  167 +* `ignore_underscore`: Allow any number of underscores in the option or flag name
  168 +* `configurable`: Specify whether an option can be configured through a config file
  169 +* `disable_flag_override`: do not allow flag values to be overridden on the command line
  170 +* `always_capture_default`: specify that the default values should be automatically captured.
  171 +* `delimiter`: A delimiter to use for capturing multiple values in a single command line string (e.g. --flag="flag,-flag2,flag3")
119 172  
120 173 An example of usage:
121 174  
... ... @@ -129,29 +182,7 @@ app.get_group() // is &quot;Required&quot;
129 182 Groups are mostly for visual organization, but an empty string for a group name will hide the option.
130 183  
131 184  
132   -## Listing of specialty options:
133   -
134   -Besides `add_option` and `add_flag`, there are several special ways to create options for sets and complex numbers.
135   -
136   -### Sets
137   -
138   -You can add a set with `add_set`, where you give a variable to set and a `std::set` of choices to pick from. There also is a `add_set_ignore_case` version which ignores case when set matching. If you use an existing set instead of an inline one, you can edit the set after adding it and changes will be reflected in the set checking and help message.
139   -
140   -```cpp
141   -int val{0};
142   -app.add_set("--even", val, {0,2,4,6,8});
143   -```
144   -
145   -### Complex numbers
146   -
147   -You can also add a complex number. This type just needs to support a `(T x, T y)` constructor and be printable. You can also pass one extra argument that will set the label of the type; by default it is "COMPLEX".
148   -
149   -```cpp
150   -std::complex<float> val{0.0F,0.0F};
151   -app.add_complex("--cplx", val);
152   -```
153   -
154   -### Windows style options (New in CLI11 1.7)
  185 +### Windows style options
155 186  
156 187 You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line:
157 188  
... ... @@ -160,12 +191,13 @@ You can also set the app setting `app-&gt;allow_windows_style_options()` to allow w
160 191 * `/long` (long flag)
161 192 * `/file filename` (space)
162 193 * `/file:filename` (colon)
  194 +* `/long_flag:false` (long flag with : to override the default value)
163 195  
164   -Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manor as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined.
  196 +Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manner as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined.
165 197  
166 198 ## Parse configuration
167 199  
168   -How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse.
  200 +How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the type or function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse.
169 201  
170 202 ### examples
171 203 How options manage this is best illustrated through some examples
... ... @@ -173,7 +205,7 @@ How options manage this is best illustrated through some examples
173 205 std::string val;
174 206 app.add_option("--opt",val,"description");
175 207 ```
176   -creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size of 1. meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types.
  208 +creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size min and max of 1. Meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types.
177 209  
178 210 now for example
179 211 ```cpp
... ... @@ -203,6 +235,20 @@ app.add_flag(&quot;--opt&quot;,val,&quot;description&quot;);
203 235  
204 236 Using the add_flag methods for creating options creates an option with an expected size of 0, implying no arguments can be passed.
205 237  
  238 +```cpp
  239 +std::complex<double> val;
  240 +app.add_option("--opt",val,"description");
  241 +```
  242 +
  243 +triggers the complex number type which has a min of 1 and max of 2, so 1 or 2 strings can be passed. Complex number conversion supports arguments of the form "1+2j" or "1","2", or "1" "2i". The imaginary number symbols `i` and `j` are interchangeable in this context.
  244 +
  245 +
  246 +```cpp
  247 +std::vector<std::vector<int>> val;
  248 +app.add_option("--opt",val,"description");
  249 +```
  250 +has a type size of 1 to (1<<30).
  251 +
206 252 ### Customization
207 253  
208 254 The `type_size(N)`, `type_size(Nmin, Nmax)`, `expected(N)`, `expected(Nmin,Nmax)`, and `allow_extra_args()` can be used to customize an option. For example
... ... @@ -214,5 +260,9 @@ opt-&gt;expected(0,1);
214 260 ```
215 261 will create a hybrid option, that can exist on its own in which case the value "vvv" is used or if a value is given that value will be used.
216 262  
217   -[^1]: For example, enums are not printable to `std::cout`.
218   -[^2]: There is a small difference. An combined unlimited option will not prioritize over a positional that could still accept values.
  263 +There are some additional options that can be specified to modify an option for specific cases
  264 +- `->run_callback_for_default()` will specify that the callback should be executed when a default_val is set. This is set automatically when appropriate though it can be turned on or off and any user specified callback for an option will be executed when the default value for an option is set.
  265 +
  266 +## Unusual circumstances
  267 +There are a few cases where some things break down in the type system managing options and definitions. Using the `add_option` method defines a lambda function to extract a default value if required. In most cases this either straightforward or a failure is detected automatically and handled. But in a few cases a streaming template is available that several layers down may not actually be defined. The conditions in CLI11 cannot detect this circumstance automatically and will result in compile error. One specific known case is `boost::optional` if the boost optional_io header is included. This header defines a template for all boost optional values even if they do no actually have a streaming operator. For example `boost::optional<std::vector>` does not have a streaming operator but one is detected since it is part of a template. For these cases a secondary method `app->add_option_no_stream(...)` is provided that bypasses this operation completely and should compile in these cases.
  268 +
... ...
1   -Subproject commit 2fe3bd994b3189899d93f1d5a881e725e046fdc2
  1 +Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e
... ...
include/CLI/App.hpp
... ... @@ -610,21 +610,39 @@ class App {
610 610 // to structs used in the evaluation can be temporary so that would cause issues.
611 611 auto Tcount = detail::type_count<AssignTo>::value;
612 612 auto XCcount = detail::type_count<ConvertTo>::value;
613   - opt->type_size((std::max)(Tcount, XCcount));
  613 + opt->type_size(detail::type_count_min<ConvertTo>::value, (std::max)(Tcount, XCcount));
614 614 opt->expected(detail::expected_count<ConvertTo>::value);
615 615 opt->run_callback_for_default();
616 616 return opt;
617 617 }
618 618  
  619 + /// Add option for assigning to a variable
  620 + template <typename AssignTo, enable_if_t<!std::is_const<AssignTo>::value, detail::enabler> = detail::dummy>
  621 + Option *add_option_no_stream(std::string option_name,
  622 + AssignTo &variable, ///< The variable to set
  623 + std::string option_description = "") {
  624 +
  625 + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing
  626 + return detail::lexical_conversion<AssignTo, AssignTo>(res, variable);
  627 + };
  628 +
  629 + Option *opt = add_option(option_name, fun, option_description, false, []() { return std::string{}; });
  630 + opt->type_name(detail::type_name<AssignTo>());
  631 + opt->type_size(detail::type_count_min<AssignTo>::value, detail::type_count<AssignTo>::value);
  632 + opt->expected(detail::expected_count<AssignTo>::value);
  633 + opt->run_callback_for_default();
  634 + return opt;
  635 + }
  636 +
619 637 /// Add option for a callback of a specific type
620   - template <typename T>
  638 + template <typename ArgType>
621 639 Option *add_option_function(std::string option_name,
622   - const std::function<void(const T &)> &func, ///< the callback to execute
  640 + const std::function<void(const ArgType &)> &func, ///< the callback to execute
623 641 std::string option_description = "") {
624 642  
625 643 auto fun = [func](const CLI::results_t &res) {
626   - T variable;
627   - bool result = detail::lexical_conversion<T, T>(res, variable);
  644 + ArgType variable;
  645 + bool result = detail::lexical_conversion<ArgType, ArgType>(res, variable);
628 646 if(result) {
629 647 func(variable);
630 648 }
... ... @@ -632,15 +650,15 @@ class App {
632 650 };
633 651  
634 652 Option *opt = add_option(option_name, std::move(fun), option_description, false);
635   - opt->type_name(detail::type_name<T>());
636   - opt->type_size(detail::type_count<T>::value);
637   - opt->expected(detail::expected_count<T>::value);
  653 + opt->type_name(detail::type_name<ArgType>());
  654 + opt->type_size(detail::type_count_min<ArgType>::value, detail::type_count<ArgType>::value);
  655 + opt->expected(detail::expected_count<ArgType>::value);
638 656 return opt;
639 657 }
640 658  
641 659 /// Add option with no description or variable assignment
642 660 Option *add_option(std::string option_name) {
643   - return add_option(option_name, CLI::callback_t(), std::string{}, false);
  661 + return add_option(option_name, CLI::callback_t{}, std::string{}, false);
644 662 }
645 663  
646 664 /// Add option with description but with no variable assignment or callback
... ... @@ -749,7 +767,7 @@ class App {
749 767 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
750 768 /// that can be converted from a string
751 769 template <typename T,
752   - enable_if_t<!is_vector<T>::value && !std::is_const<T>::value &&
  770 + enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
753 771 (!std::is_integral<T>::value || is_bool<T>::value) &&
754 772 !std::is_constructible<std::function<void(int)>, T>::value,
755 773 detail::enabler> = detail::dummy>
... ... @@ -873,7 +891,7 @@ class App {
873 891 return opt;
874 892 }
875 893  
876   - /// Add a complex number
  894 + /// Add a complex number DEPRECATED --use add_option instead
877 895 template <typename T, typename XC = double>
878 896 Option *add_complex(std::string option_name,
879 897 T &variable,
... ... @@ -2660,7 +2678,12 @@ class App {
2660 2678  
2661 2679 // Get a reference to the pointer to make syntax bearable
2662 2680 Option_p &op = *op_ptr;
2663   -
  2681 + /// if we require a separator add it here
  2682 + if(op->get_inject_separator()) {
  2683 + if(!op->results().empty() && !op->results().back().empty()) {
  2684 + op->add_result(std::string{});
  2685 + }
  2686 + }
2664 2687 int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
2665 2688 int max_num = op->get_items_expected_max();
2666 2689  
... ... @@ -2725,7 +2748,7 @@ class App {
2725 2748 }
2726 2749  
2727 2750 // if we only partially completed a type then add an empty string for later processing
2728   - if(min_num > 0 && op->get_type_size_max() != min_num && collected % op->get_type_size_max() != 0) {
  2751 + if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) {
2729 2752 op->add_result(std::string{});
2730 2753 }
2731 2754  
... ...
include/CLI/Option.hpp
... ... @@ -315,7 +315,7 @@ class Option : public OptionBase&lt;Option&gt; {
315 315 /// results after reduction
316 316 results_t proc_results_{};
317 317 /// enumeration for the option state machine
318   - enum class option_state {
  318 + enum class option_state : char {
319 319 parsing = 0, //!< The option is currently collecting parsed results
320 320 validated = 2, //!< the results have been validated
321 321 reduced = 4, //!< a subset of results has been generated
... ... @@ -329,6 +329,8 @@ class Option : public OptionBase&lt;Option&gt; {
329 329 bool flag_like_{false};
330 330 /// Control option to run the callback to set the default
331 331 bool run_callback_for_default_{false};
  332 + /// flag indicating a separator needs to be injected after each argument call
  333 + bool inject_separator_{false};
332 334 ///@}
333 335  
334 336 /// Making an option by hand is not defined, it must be made by the App class
... ... @@ -658,6 +660,9 @@ class Option : public OptionBase&lt;Option&gt; {
658 660 /// The maximum number of arguments the option expects
659 661 int get_type_size_max() const { return type_size_max_; }
660 662  
  663 + /// The number of arguments the option expects
  664 + int get_inject_separator() const { return inject_separator_; }
  665 +
661 666 /// The environment variable associated to this value
662 667 std::string get_envname() const { return envname_; }
663 668  
... ... @@ -963,8 +968,8 @@ class Option : public OptionBase&lt;Option&gt; {
963 968 return this;
964 969 }
965 970  
966   - /// Get a copy of the results
967   - results_t results() const { return results_; }
  971 + /// Get the current complete results set
  972 + const results_t &results() const { return results_; }
968 973  
969 974 /// Get a copy of the results
970 975 results_t reduced_results() const {
... ... @@ -986,8 +991,7 @@ class Option : public OptionBase&lt;Option&gt; {
986 991 }
987 992  
988 993 /// Get the results as a specified type
989   - template <typename T, enable_if_t<!std::is_const<T>::value, detail::enabler> = detail::dummy>
990   - void results(T &output) const {
  994 + template <typename T> void results(T &output) const {
991 995 bool retval;
992 996 if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
993 997 const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
... ... @@ -1054,6 +1058,8 @@ class Option : public OptionBase&lt;Option&gt; {
1054 1058 type_size_max_ = option_type_size;
1055 1059 if(type_size_max_ < detail::expected_max_vector_size) {
1056 1060 type_size_min_ = option_type_size;
  1061 + } else {
  1062 + inject_separator_ = true;
1057 1063 }
1058 1064 if(type_size_max_ == 0)
1059 1065 required_ = false;
... ... @@ -1079,9 +1085,15 @@ class Option : public OptionBase&lt;Option&gt; {
1079 1085 if(type_size_max_ == 0) {
1080 1086 required_ = false;
1081 1087 }
  1088 + if(type_size_max_ >= detail::expected_max_vector_size) {
  1089 + inject_separator_ = true;
  1090 + }
1082 1091 return this;
1083 1092 }
1084 1093  
  1094 + /// Set the value of the separator injection flag
  1095 + void inject_separator(bool value = true) { inject_separator_ = value; }
  1096 +
1085 1097 /// Set a capture function for the default. Mostly used by App.
1086 1098 Option *default_function(const std::function<std::string()> &func) {
1087 1099 default_function_ = func;
... ... @@ -1157,7 +1169,7 @@ class Option : public OptionBase&lt;Option&gt; {
1157 1169 }
1158 1170  
1159 1171 for(std::string &result : res) {
1160   - if(result.empty() && type_size_max_ != type_size_min_ && index >= 0) {
  1172 + if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) {
1161 1173 index = 0; // reset index for variable size chunks
1162 1174 continue;
1163 1175 }
... ...
include/CLI/StringTools.hpp
... ... @@ -190,6 +190,12 @@ inline bool valid_name_string(const std::string &amp;str) {
190 190 return true;
191 191 }
192 192  
  193 +/// check if a string is a container segment separator (empty or "%%"
  194 +inline bool is_separator(const std::string &str) {
  195 + static const std::string sep("%%");
  196 + return (str.empty() || str == sep);
  197 +}
  198 +
193 199 /// Verify that str consists of letters only
194 200 inline bool isalpha(const std::string &str) {
195 201 return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
... ...
include/CLI/TypeTools.hpp
... ... @@ -45,15 +45,6 @@ template &lt;typename... Ts&gt; using void_t = typename make_void&lt;Ts...&gt;::type;
45 45 /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine
46 46 template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type;
47 47  
48   -/// Check to see if something is a vector (fail check by default)
49   -template <typename T> struct is_vector : std::false_type {};
50   -
51   -/// Check to see if something is a vector (true if actually a vector)
52   -template <class T, class A> struct is_vector<std::vector<T, A>> : std::true_type {};
53   -
54   -/// Check to see if something is a vector (true if actually a const vector)
55   -template <class T, class A> struct is_vector<const std::vector<T, A>> : std::true_type {};
56   -
57 48 /// Check to see if something is bool (fail check by default)
58 49 template <typename T> struct is_bool : std::false_type {};
59 50  
... ... @@ -195,6 +186,17 @@ template &lt;typename T, typename S = std::istringstream&gt; class is_istreamable {
195 186 static constexpr bool value = decltype(test<T, S>(0))::value;
196 187 };
197 188  
  189 +/// Check for complex
  190 +template <typename T> class is_complex {
  191 + template <typename TT>
  192 + static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type());
  193 +
  194 + template <typename> static auto test(...) -> std::false_type;
  195 +
  196 + public:
  197 + static constexpr bool value = decltype(test<T>(0))::value;
  198 +};
  199 +
198 200 /// Templated operation to get a value from a stream
199 201 template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
200 202 bool from_stream(const std::string &istring, T &obj) {
... ... @@ -209,12 +211,49 @@ bool from_stream(const std::string &amp; /*istring*/, T &amp; /*obj*/) {
209 211 return false;
210 212 }
211 213  
  214 +// check to see if an object is a mutable container (fail by default)
  215 +template <typename T, typename _ = void> struct is_mutable_container : std::false_type {};
  216 +
  217 +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and
  218 +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed
  219 +/// from a std::string
  220 +template <typename T>
  221 +struct is_mutable_container<
  222 + T,
  223 + conditional_t<false,
  224 + void_t<typename T::value_type,
  225 + decltype(std::declval<T>().end()),
  226 + decltype(std::declval<T>().clear()),
  227 + decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
  228 + std::declval<const typename T::value_type &>()))>,
  229 + void>>
  230 + : public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {};
  231 +
  232 +// check to see if an object is a mutable container (fail by default)
  233 +template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
  234 +
  235 +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an en end
  236 +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from
  237 +/// a std::string
  238 +template <typename T>
  239 +struct is_readable_container<
  240 + T,
  241 + conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>>
  242 + : public std::true_type {};
  243 +
  244 +// check to see if an object is a wrapper (fail by default)
  245 +template <typename T, typename _ = void> struct is_wrapper : std::false_type {};
  246 +
  247 +// check if an object is a is a wrapper (it has a value_type defined)
  248 +template <typename T>
  249 +struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {};
  250 +
212 251 // Check for tuple like types, as in classes with a tuple_size type trait
213 252 template <typename S> class is_tuple_like {
214 253 template <typename SS>
215 254 // static auto test(int)
216 255 // -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
217   - static auto test(int) -> decltype(std::tuple_size<SS>::value, std::true_type{});
  256 + static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{});
218 257 template <typename> static auto test(...) -> std::false_type;
219 258  
220 259 public:
... ... @@ -249,20 +288,19 @@ std::string to_string(T &amp;&amp;value) {
249 288 /// If conversion is not supported, return an empty string (streaming is not supported for that type)
250 289 template <typename T,
251 290 enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
252   - !is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
  291 + !is_readable_container<typename std::remove_const<T>::type>::value,
253 292 detail::enabler> = detail::dummy>
254 293 std::string to_string(T &&) {
255 294 return std::string{};
256 295 }
257 296  
258   -/// convert a vector to a string
  297 +/// convert a readable container to a string
259 298 template <typename T,
260 299 enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
261   - is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
  300 + is_readable_container<T>::value,
262 301 detail::enabler> = detail::dummy>
263 302 std::string to_string(T &&variable) {
264 303 std::vector<std::string> defaults;
265   - defaults.reserve(variable.size());
266 304 auto cval = variable.begin();
267 305 auto end = variable.end();
268 306 while(cval != end) {
... ... @@ -306,25 +344,142 @@ auto value_string(const T &amp;value) -&gt; decltype(to_string(value)) {
306 344 return to_string(value);
307 345 }
308 346  
  347 +/// temple to get the underlying value type if it exists or use a default
  348 +template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; };
  349 +
  350 +/// Type size for regular object types that do not look like a tuple
  351 +template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> {
  352 + using type = typename T::value_type;
  353 +};
  354 +
309 355 /// This will only trigger for actual void type
310   -template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
  356 +template <typename T, typename Enable = void> struct type_count_base { static const int value{0}; };
  357 +
  358 +/// Type size for regular object types that do not look like a tuple
  359 +template <typename T>
  360 +struct type_count_base<T,
  361 + typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value &&
  362 + !std::is_void<T>::value>::type> {
  363 + static constexpr int value{1};
  364 +};
  365 +
  366 +/// the base tuple size
  367 +template <typename T>
  368 +struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> {
  369 + static constexpr int value{std::tuple_size<T>::value};
  370 +};
  371 +
  372 +/// Type count base for containers is the type_count_base of the individual element
  373 +template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
  374 + static constexpr int value{type_count_base<typename T::value_type>::value};
  375 +};
311 376  
312 377 /// Set of overloads to get the type size of an object
  378 +
  379 +/// forward declare the subtype_count structure
  380 +template <typename T> struct subtype_count;
  381 +
  382 +/// forward declare the subtype_count_min structure
  383 +template <typename T> struct subtype_count_min;
  384 +
  385 +/// This will only trigger for actual void type
  386 +template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
  387 +
  388 +/// Type size for regular object types that do not look like a tuple
  389 +template <typename T>
  390 +struct type_count<T,
  391 + typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value &&
  392 + !std::is_void<T>::value>::type> {
  393 + static constexpr int value{1};
  394 +};
  395 +
  396 +/// Type size for complex since it sometimes looks like a wrapper
  397 +template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> {
  398 + static constexpr int value{2};
  399 +};
  400 +
  401 +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
  402 +template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
  403 + static constexpr int value{subtype_count<typename T::value_type>::value};
  404 +};
  405 +
  406 +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes)
  407 +template <typename T>
  408 +struct type_count<T,
  409 + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value &&
  410 + !is_mutable_container<T>::value>::type> {
  411 + static constexpr int value{type_count<typename T::value_type>::value};
  412 +};
  413 +
  414 +/// 0 if the index > tuple size
  415 +template <typename T, std::size_t I>
  416 +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() {
  417 + return 0;
  418 +}
  419 +
  420 +/// Recursively generate the tuple type name
  421 +template <typename T, std::size_t I>
  422 + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() {
  423 + return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>();
  424 +}
  425 +
  426 +/// Get the type size of the sum of type sizes for all the individual tuple types
313 427 template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
314   - static constexpr int value{std::tuple_size<T>::value};
  428 + static constexpr int value{tuple_type_size<T, 0>()};
  429 +};
  430 +
  431 +/// definition of subtype count
  432 +template <typename T> struct subtype_count {
  433 + static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value};
315 434 };
  435 +
  436 +/// This will only trigger for actual void type
  437 +template <typename T, typename Enable = void> struct type_count_min { static const int value{0}; };
  438 +
316 439 /// Type size for regular object types that do not look like a tuple
317 440 template <typename T>
318   -struct type_count<
  441 +struct type_count_min<
319 442 T,
320   - typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> {
  443 + typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value &&
  444 + !is_complex<T>::value && !std::is_void<T>::value>::type> {
  445 + static constexpr int value{type_count<T>::value};
  446 +};
  447 +
  448 +/// Type size for complex since it sometimes looks like a wrapper
  449 +template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> {
321 450 static constexpr int value{1};
322 451 };
323 452  
324   -/// Type size of types that look like a vector
325   -template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> {
326   - static constexpr int value{is_vector<typename T::value_type>::value ? expected_max_vector_size
327   - : type_count<typename T::value_type>::value};
  453 +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
  454 +template <typename T>
  455 +struct type_count_min<
  456 + T,
  457 + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> {
  458 + static constexpr int value{subtype_count_min<typename T::value_type>::value};
  459 +};
  460 +
  461 +/// 0 if the index > tuple size
  462 +template <typename T, std::size_t I>
  463 +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() {
  464 + return 0;
  465 +}
  466 +
  467 +/// Recursively generate the tuple type name
  468 +template <typename T, std::size_t I>
  469 + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() {
  470 + return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>();
  471 +}
  472 +
  473 +/// Get the type size of the sum of type sizes for all the individual tuple types
  474 +template <typename T> struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
  475 + static constexpr int value{tuple_type_size_min<T, 0>()};
  476 +};
  477 +
  478 +/// definition of subtype count
  479 +template <typename T> struct subtype_count_min {
  480 + static constexpr int value{is_mutable_container<T>::value
  481 + ? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0)
  482 + : type_count_min<T>::value};
328 483 };
329 484  
330 485 /// This will only trigger for actual void type
... ... @@ -332,14 +487,22 @@ template &lt;typename T, typename Enable = void&gt; struct expected_count { static con
332 487  
333 488 /// For most types the number of expected items is 1
334 489 template <typename T>
335   -struct expected_count<T, typename std::enable_if<!is_vector<T>::value && !std::is_void<T>::value>::type> {
  490 +struct expected_count<T,
  491 + typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value &&
  492 + !std::is_void<T>::value>::type> {
336 493 static constexpr int value{1};
337 494 };
338 495 /// number of expected items in a vector
339   -template <typename T> struct expected_count<T, typename std::enable_if<is_vector<T>::value>::type> {
  496 +template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
340 497 static constexpr int value{expected_max_vector_size};
341 498 };
342 499  
  500 +/// number of expected items in a vector
  501 +template <typename T>
  502 +struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value && is_wrapper<T>::value>::type> {
  503 + static constexpr int value{expected_count<typename T::value_type>::value};
  504 +};
  505 +
343 506 // Enumeration of the different supported categorizations of objects
344 507 enum class object_category : int {
345 508 integral_value = 2,
... ... @@ -350,12 +513,15 @@ enum class object_category : int {
350 513 number_constructible = 12,
351 514 double_constructible = 14,
352 515 integer_constructible = 16,
353   - vector_value = 30,
354   - tuple_value = 35,
355   - // string assignable or greater used in a condition so anything string like must come last
356   - string_assignable = 50,
357   - string_constructible = 60,
358   - other = 200,
  516 + // string like types
  517 + string_assignable = 23,
  518 + string_constructible = 24,
  519 + other = 45,
  520 + // special wrapper or container types
  521 + wrapper_value = 50,
  522 + complex_number = 60,
  523 + tuple_value = 70,
  524 + container_value = 80,
359 525  
360 526 };
361 527  
... ... @@ -392,10 +558,9 @@ template &lt;typename T&gt; struct classify_object&lt;T, typename std::enable_if&lt;std::is_
392 558  
393 559 /// String and similar direct assignment
394 560 template <typename T>
395   -struct classify_object<
396   - T,
397   - typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
398   - std::is_assignable<T &, std::string>::value && !is_vector<T>::value>::type> {
  561 +struct classify_object<T,
  562 + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
  563 + std::is_assignable<T &, std::string>::value>::type> {
399 564 static constexpr object_category value{object_category::string_assignable};
400 565 };
401 566  
... ... @@ -404,8 +569,8 @@ template &lt;typename T&gt;
404 569 struct classify_object<
405 570 T,
406 571 typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
407   - !std::is_assignable<T &, std::string>::value &&
408   - std::is_constructible<T, std::string>::value && !is_vector<T>::value>::type> {
  572 + !std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) &&
  573 + std::is_constructible<T, std::string>::value>::type> {
409 574 static constexpr object_category value{object_category::string_constructible};
410 575 };
411 576  
... ... @@ -414,23 +579,35 @@ template &lt;typename T&gt; struct classify_object&lt;T, typename std::enable_if&lt;std::is_
414 579 static constexpr object_category value{object_category::enumeration};
415 580 };
416 581  
  582 +template <typename T> struct classify_object<T, typename std::enable_if<is_complex<T>::value>::type> {
  583 + static constexpr object_category value{object_category::complex_number};
  584 +};
  585 +
417 586 /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point,
418 587 /// vectors, and enumerations
419 588 template <typename T> struct uncommon_type {
420 589 using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
421 590 !std::is_assignable<T &, std::string>::value &&
422   - !std::is_constructible<T, std::string>::value && !is_vector<T>::value &&
423   - !std::is_enum<T>::value,
  591 + !std::is_constructible<T, std::string>::value && !is_complex<T>::value &&
  592 + !is_mutable_container<T>::value && !std::is_enum<T>::value,
424 593 std::true_type,
425 594 std::false_type>::type;
426 595 static constexpr bool value = type::value;
427 596 };
428 597  
  598 +/// wrapper type
  599 +template <typename T>
  600 +struct classify_object<T,
  601 + typename std::enable_if<(!is_mutable_container<T>::value && is_wrapper<T>::value &&
  602 + !is_tuple_like<T>::value && uncommon_type<T>::value)>::type> {
  603 + static constexpr object_category value{object_category::wrapper_value};
  604 +};
  605 +
429 606 /// Assignable from double or int
430 607 template <typename T>
431 608 struct classify_object<T,
432 609 typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
433   - is_direct_constructible<T, double>::value &&
  610 + !is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
434 611 is_direct_constructible<T, int>::value>::type> {
435 612 static constexpr object_category value{object_category::number_constructible};
436 613 };
... ... @@ -439,7 +616,7 @@ struct classify_object&lt;T,
439 616 template <typename T>
440 617 struct classify_object<T,
441 618 typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
442   - !is_direct_constructible<T, double>::value &&
  619 + !is_wrapper<T>::value && !is_direct_constructible<T, double>::value &&
443 620 is_direct_constructible<T, int>::value>::type> {
444 621 static constexpr object_category value{object_category::integer_constructible};
445 622 };
... ... @@ -448,24 +625,30 @@ struct classify_object&lt;T,
448 625 template <typename T>
449 626 struct classify_object<T,
450 627 typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
451   - is_direct_constructible<T, double>::value &&
  628 + !is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
452 629 !is_direct_constructible<T, int>::value>::type> {
453 630 static constexpr object_category value{object_category::double_constructible};
454 631 };
455 632  
456 633 /// Tuple type
457 634 template <typename T>
458   -struct classify_object<T,
459   - typename std::enable_if<(type_count<T>::value >= 2 && !is_vector<T>::value) ||
460   - (is_tuple_like<T>::value && uncommon_type<T>::value &&
461   - !is_direct_constructible<T, double>::value &&
462   - !is_direct_constructible<T, int>::value)>::type> {
  635 +struct classify_object<
  636 + T,
  637 + typename std::enable_if<is_tuple_like<T>::value &&
  638 + ((type_count<T>::value >= 2 && !is_wrapper<T>::value) ||
  639 + (uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
  640 + !is_direct_constructible<T, int>::value))>::type> {
463 641 static constexpr object_category value{object_category::tuple_value};
  642 + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be
  643 + // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which
  644 + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2
  645 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out
  646 + // those cases that are caught by other object classifications
464 647 };
465 648  
466   -/// Vector type
467   -template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> {
468   - static constexpr object_category value{object_category::vector_value};
  649 +/// container type
  650 +template <typename T> struct classify_object<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
  651 + static constexpr object_category value{object_category::container_value};
469 652 };
470 653  
471 654 // Type name print
... ... @@ -511,31 +694,53 @@ constexpr const char *type_name() {
511 694 return "BOOLEAN";
512 695 }
513 696  
  697 +/// Print name for enumeration types
  698 +template <typename T,
  699 + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy>
  700 +constexpr const char *type_name() {
  701 + return "COMPLEX";
  702 +}
  703 +
514 704 /// Print for all other types
515 705 template <typename T,
516   - enable_if_t<classify_object<T>::value >= object_category::string_assignable, detail::enabler> = detail::dummy>
  706 + enable_if_t<classify_object<T>::value >= object_category::string_assignable &&
  707 + classify_object<T>::value <= object_category::other,
  708 + detail::enabler> = detail::dummy>
517 709 constexpr const char *type_name() {
518 710 return "TEXT";
519 711 }
  712 +/// typename for tuple value
  713 +template <typename T,
  714 + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2,
  715 + detail::enabler> = detail::dummy>
  716 +std::string type_name(); // forward declaration
  717 +
  718 +/// Generate type name for a wrapper or container value
  719 +template <typename T,
  720 + enable_if_t<classify_object<T>::value == object_category::container_value ||
  721 + classify_object<T>::value == object_category::wrapper_value,
  722 + detail::enabler> = detail::dummy>
  723 +std::string type_name(); // forward declaration
520 724  
521 725 /// Print name for single element tuple types
522 726 template <typename T,
523   - enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count<T>::value == 1,
  727 + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value == 1,
524 728 detail::enabler> = detail::dummy>
525 729 inline std::string type_name() {
526   - return type_name<typename std::tuple_element<0, T>::type>();
  730 + return type_name<typename std::decay<typename std::tuple_element<0, T>::type>::type>();
527 731 }
528 732  
529 733 /// Empty string if the index > tuple size
530 734 template <typename T, std::size_t I>
531   -inline typename std::enable_if<I == type_count<T>::value, std::string>::type tuple_name() {
  735 +inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_name() {
532 736 return std::string{};
533 737 }
534 738  
535 739 /// Recursively generate the tuple type name
536 740 template <typename T, std::size_t I>
537   - inline typename std::enable_if < I<type_count<T>::value, std::string>::type tuple_name() {
538   - std::string str = std::string(type_name<typename std::tuple_element<I, T>::type>()) + ',' + tuple_name<T, I + 1>();
  741 +inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() {
  742 + std::string str = std::string(type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()) +
  743 + ',' + tuple_name<T, I + 1>();
539 744 if(str.back() == ',')
540 745 str.pop_back();
541 746 return str;
... ... @@ -543,17 +748,19 @@ template &lt;typename T, std::size_t I&gt;
543 748  
544 749 /// Print type name for tuples with 2 or more elements
545 750 template <typename T,
546   - enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count<T>::value >= 2,
547   - detail::enabler> = detail::dummy>
548   -std::string type_name() {
  751 + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2,
  752 + detail::enabler>>
  753 +inline std::string type_name() {
549 754 auto tname = std::string(1, '[') + tuple_name<T, 0>();
550 755 tname.push_back(']');
551 756 return tname;
552 757 }
553 758  
554   -/// This one should not be used normally, since vector types print the internal type
  759 +/// get the type name for a type that has a value_type member
555 760 template <typename T,
556   - enable_if_t<classify_object<T>::value == object_category::vector_value, detail::enabler> = detail::dummy>
  761 + enable_if_t<classify_object<T>::value == object_category::container_value ||
  762 + classify_object<T>::value == object_category::wrapper_value,
  763 + detail::enabler>>
557 764 inline std::string type_name() {
558 765 return type_name<typename T::value_type>();
559 766 }
... ... @@ -671,6 +878,38 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
671 878 }
672 879 }
673 880  
  881 +/// complex
  882 +template <typename T,
  883 + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy>
  884 +bool lexical_cast(const std::string &input, T &output) {
  885 + using XC = typename wrapped_type<T, double>::type;
  886 + XC x{0.0}, y{0.0};
  887 + auto str1 = input;
  888 + bool worked = false;
  889 + auto nloc = str1.find_last_of("+-");
  890 + if(nloc != std::string::npos && nloc > 0) {
  891 + worked = detail::lexical_cast(str1.substr(0, nloc), x);
  892 + str1 = str1.substr(nloc);
  893 + if(str1.back() == 'i' || str1.back() == 'j')
  894 + str1.pop_back();
  895 + worked = worked && detail::lexical_cast(str1, y);
  896 + } else {
  897 + if(str1.back() == 'i' || str1.back() == 'j') {
  898 + str1.pop_back();
  899 + worked = detail::lexical_cast(str1, y);
  900 + x = XC{0};
  901 + } else {
  902 + worked = detail::lexical_cast(str1, x);
  903 + y = XC{0};
  904 + }
  905 + }
  906 + if(worked) {
  907 + output = T{x, y};
  908 + return worked;
  909 + }
  910 + return from_stream(input, output);
  911 +}
  912 +
674 913 /// String and similar direct assignment
675 914 template <typename T,
676 915 enable_if_t<classify_object<T>::value == object_category::string_assignable, detail::enabler> = detail::dummy>
... ... @@ -701,6 +940,18 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
701 940 return true;
702 941 }
703 942  
  943 +/// wrapper types
  944 +template <typename T,
  945 + enable_if_t<classify_object<T>::value == object_category::wrapper_value, detail::enabler> = detail::dummy>
  946 +bool lexical_cast(const std::string &input, T &output) {
  947 + typename T::value_type val;
  948 + if(lexical_cast(input, val)) {
  949 + output = T{val};
  950 + return true;
  951 + }
  952 + return from_stream(input, output);
  953 +}
  954 +
704 955 /// Assignable from double or int
705 956 template <
706 957 typename T,
... ... @@ -756,38 +1007,40 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
756 1007 }
757 1008  
758 1009 /// Assign a value through lexical cast operations
759   -template <
760   - typename T,
761   - typename XC,
762   - enable_if_t<std::is_same<T, XC>::value && (classify_object<T>::value == object_category::string_assignable ||
763   - classify_object<T>::value == object_category::string_constructible),
764   - detail::enabler> = detail::dummy>
765   -bool lexical_assign(const std::string &input, T &output) {
  1010 +/// Strings can be empty so we need to do a little different
  1011 +template <typename AssignTo,
  1012 + typename ConvertTo,
  1013 + enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
  1014 + (classify_object<AssignTo>::value == object_category::string_assignable ||
  1015 + classify_object<AssignTo>::value == object_category::string_constructible),
  1016 + detail::enabler> = detail::dummy>
  1017 +bool lexical_assign(const std::string &input, AssignTo &output) {
766 1018 return lexical_cast(input, output);
767 1019 }
768 1020  
769 1021 /// Assign a value through lexical cast operations
770   -template <typename T,
771   - typename XC,
772   - enable_if_t<std::is_same<T, XC>::value && classify_object<T>::value != object_category::string_assignable &&
773   - classify_object<T>::value != object_category::string_constructible,
  1022 +template <typename AssignTo,
  1023 + typename ConvertTo,
  1024 + enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
  1025 + classify_object<AssignTo>::value != object_category::string_assignable &&
  1026 + classify_object<AssignTo>::value != object_category::string_constructible,
774 1027 detail::enabler> = detail::dummy>
775   -bool lexical_assign(const std::string &input, T &output) {
  1028 +bool lexical_assign(const std::string &input, AssignTo &output) {
776 1029 if(input.empty()) {
777   - output = T{};
  1030 + output = AssignTo{};
778 1031 return true;
779 1032 }
780 1033 return lexical_cast(input, output);
781 1034 }
782 1035  
783 1036 /// Assign a value converted from a string in lexical cast to the output value directly
784   -template <
785   - typename T,
786   - typename XC,
787   - enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
788   -bool lexical_assign(const std::string &input, T &output) {
789   - XC val{};
790   - bool parse_result = (!input.empty()) ? lexical_cast<XC>(input, val) : true;
  1037 +template <typename AssignTo,
  1038 + typename ConvertTo,
  1039 + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, ConvertTo &>::value,
  1040 + detail::enabler> = detail::dummy>
  1041 +bool lexical_assign(const std::string &input, AssignTo &output) {
  1042 + ConvertTo val{};
  1043 + bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true;
791 1044 if(parse_result) {
792 1045 output = val;
793 1046 }
... ... @@ -795,195 +1048,325 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) {
795 1048 }
796 1049  
797 1050 /// Assign a value from a lexical cast through constructing a value and move assigning it
798   -template <typename T,
799   - typename XC,
800   - enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value &&
801   - std::is_move_assignable<T>::value,
802   - detail::enabler> = detail::dummy>
803   -bool lexical_assign(const std::string &input, T &output) {
804   - XC val{};
805   - bool parse_result = input.empty() ? true : lexical_cast<XC>(input, val);
  1051 +template <
  1052 + typename AssignTo,
  1053 + typename ConvertTo,
  1054 + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, ConvertTo &>::value &&
  1055 + std::is_move_assignable<AssignTo>::value,
  1056 + detail::enabler> = detail::dummy>
  1057 +bool lexical_assign(const std::string &input, AssignTo &output) {
  1058 + ConvertTo val{};
  1059 + bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val);
806 1060 if(parse_result) {
807   - output = T(val); // use () form of constructor to allow some implicit conversions
  1061 + output = AssignTo(val); // use () form of constructor to allow some implicit conversions
808 1062 }
809 1063 return parse_result;
810 1064 }
811   -/// Lexical conversion if there is only one element
812   -template <
813   - typename T,
814   - typename XC,
815   - enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value && !is_vector<XC>::value,
816   - detail::enabler> = detail::dummy>
817   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
818   - return lexical_assign<T, XC>(strings[0], output);
  1065 +
  1066 +/// primary lexical conversion operation, 1 string to 1 type of some kind
  1067 +template <typename AssignTo,
  1068 + typename ConvertTo,
  1069 + enable_if_t<classify_object<ConvertTo>::value <= object_category::other &&
  1070 + classify_object<AssignTo>::value <= object_category::wrapper_value,
  1071 + detail::enabler> = detail::dummy>
  1072 +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
  1073 + return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
819 1074 }
820 1075  
821   -/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor
822   -template <typename T,
823   - typename XC,
824   - enable_if_t<type_count<T>::value == 1 && type_count<XC>::value == 2, detail::enabler> = detail::dummy>
825   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
826   - typename std::tuple_element<0, XC>::type v1;
827   - typename std::tuple_element<1, XC>::type v2;
  1076 +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element
  1077 +/// constructor
  1078 +template <typename AssignTo,
  1079 + typename ConvertTo,
  1080 + enable_if_t<(type_count<AssignTo>::value <= 2) && expected_count<AssignTo>::value == 1 &&
  1081 + is_tuple_like<ConvertTo>::value && type_count_base<ConvertTo>::value == 2,
  1082 + detail::enabler> = detail::dummy>
  1083 +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
  1084 + // the remove const is to handle pair types coming from a container
  1085 + typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1;
  1086 + typename std::tuple_element<1, ConvertTo>::type v2;
828 1087 bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1);
829 1088 if(strings.size() > 1) {
830 1089 retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2);
831 1090 }
832 1091 if(retval) {
833   - output = T{v1, v2};
  1092 + output = AssignTo{v1, v2};
834 1093 }
835 1094 return retval;
836 1095 }
837 1096  
838   -/// Lexical conversion of a vector types
839   -template <class T,
840   - class XC,
841   - enable_if_t<expected_count<T>::value == expected_max_vector_size &&
842   - expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 1,
  1097 +/// Lexical conversion of a container types of single elements
  1098 +template <class AssignTo,
  1099 + class ConvertTo,
  1100 + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
  1101 + type_count<ConvertTo>::value == 1,
843 1102 detail::enabler> = detail::dummy>
844   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
845   - output.clear();
846   - output.reserve(strings.size());
  1103 +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
  1104 + output.erase(output.begin(), output.end());
847 1105 for(const auto &elem : strings) {
848   -
849   - output.emplace_back();
850   - bool retval = lexical_assign<typename T::value_type, typename XC::value_type>(elem, output.back());
  1106 + typename AssignTo::value_type out;
  1107 + bool retval = lexical_assign<typename AssignTo::value_type, typename ConvertTo::value_type>(elem, out);
851 1108 if(!retval) {
852 1109 return false;
853 1110 }
  1111 + output.insert(output.end(), std::move(out));
854 1112 }
855 1113 return (!output.empty());
856 1114 }
857 1115  
858   -/// Lexical conversion of a vector types with type size of two
859   -template <class T,
860   - class XC,
861   - enable_if_t<expected_count<T>::value == expected_max_vector_size &&
862   - expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 2,
863   - detail::enabler> = detail::dummy>
864   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
865   - output.clear();
866   - for(std::size_t ii = 0; ii < strings.size(); ii += 2) {
  1116 +/// Lexical conversion for complex types
  1117 +template <class AssignTo, class ConvertTo, enable_if_t<is_complex<ConvertTo>::value, detail::enabler> = detail::dummy>
  1118 +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
867 1119  
868   - typename std::tuple_element<0, typename XC::value_type>::type v1;
869   - typename std::tuple_element<1, typename XC::value_type>::type v2;
870   - bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[ii], v1);
871   - if(strings.size() > ii + 1) {
872   - retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[ii + 1], v2);
  1120 + if(strings.size() >= 2 && !strings[1].empty()) {
  1121 + using XC2 = typename wrapped_type<ConvertTo, double>::type;
  1122 + XC2 x{0.0}, y{0.0};
  1123 + auto str1 = strings[1];
  1124 + if(str1.back() == 'i' || str1.back() == 'j') {
  1125 + str1.pop_back();
873 1126 }
874   - if(retval) {
875   - output.emplace_back(v1, v2);
876   - } else {
877   - return false;
  1127 + auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y);
  1128 + if(worked) {
  1129 + output = ConvertTo{x, y};
878 1130 }
  1131 + return worked;
  1132 + } else {
  1133 + return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
879 1134 }
880   - return (!output.empty());
881 1135 }
882 1136  
883 1137 /// Conversion to a vector type using a particular single type as the conversion type
884   -template <class T,
885   - class XC,
886   - enable_if_t<(expected_count<T>::value == expected_max_vector_size) && (expected_count<XC>::value == 1) &&
887   - (type_count<XC>::value == 1),
  1138 +template <class AssignTo,
  1139 + class ConvertTo,
  1140 + enable_if_t<is_mutable_container<AssignTo>::value && (expected_count<ConvertTo>::value == 1) &&
  1141 + (type_count<ConvertTo>::value == 1),
888 1142 detail::enabler> = detail::dummy>
889   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  1143 +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
890 1144 bool retval = true;
891 1145 output.clear();
892 1146 output.reserve(strings.size());
893 1147 for(const auto &elem : strings) {
894 1148  
895 1149 output.emplace_back();
896   - retval = retval && lexical_assign<typename T::value_type, XC>(elem, output.back());
  1150 + retval = retval && lexical_assign<typename AssignTo::value_type, ConvertTo>(elem, output.back());
897 1151 }
898 1152 return (!output.empty()) && retval;
899 1153 }
900   -// This one is last since it can call other lexical_conversion functions
901   -/// Lexical conversion if there is only one element but the conversion type is a vector
902   -template <typename T,
903   - typename XC,
904   - enable_if_t<!is_tuple_like<T>::value && !is_vector<T>::value && is_vector<XC>::value, detail::enabler> =
905   - detail::dummy>
906   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  1154 +
  1155 +// forward declaration
  1156 +
  1157 +/// Lexical conversion of a container types with conversion type of two elements
  1158 +template <class AssignTo,
  1159 + class ConvertTo,
  1160 + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
  1161 + type_count_base<ConvertTo>::value == 2,
  1162 + detail::enabler> = detail::dummy>
  1163 +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output);
  1164 +
  1165 +/// Lexical conversion of a vector types with type_size >2 forward declaration
  1166 +template <class AssignTo,
  1167 + class ConvertTo,
  1168 + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
  1169 + type_count_base<ConvertTo>::value != 2 &&
  1170 + ((type_count<ConvertTo>::value > 2) ||
  1171 + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)),
  1172 + detail::enabler> = detail::dummy>
  1173 +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output);
  1174 +
  1175 +/// Conversion for tuples
  1176 +template <class AssignTo,
  1177 + class ConvertTo,
  1178 + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value &&
  1179 + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value ||
  1180 + type_count<ConvertTo>::value > 2),
  1181 + detail::enabler> = detail::dummy>
  1182 +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); // forward declaration
  1183 +
  1184 +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large
  1185 +/// tuple
  1186 +template <typename AssignTo,
  1187 + typename ConvertTo,
  1188 + enable_if_t<!is_tuple_like<AssignTo>::value && !is_mutable_container<AssignTo>::value &&
  1189 + classify_object<ConvertTo>::value != object_category::wrapper_value &&
  1190 + (is_mutable_container<ConvertTo>::value || type_count<ConvertTo>::value > 2),
  1191 + detail::enabler> = detail::dummy>
  1192 +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
907 1193  
908 1194 if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) {
909   - XC val;
910   - auto retval = lexical_conversion<XC, XC>(strings, val);
911   - output = T{val};
  1195 + ConvertTo val;
  1196 + auto retval = lexical_conversion<ConvertTo, ConvertTo>(strings, val);
  1197 + output = AssignTo{val};
912 1198 return retval;
913 1199 }
914   - output = T{};
  1200 + output = AssignTo{};
915 1201 return true;
916 1202 }
917 1203  
918 1204 /// function template for converting tuples if the static Index is greater than the tuple size
919   -template <class T, class XC, std::size_t I>
920   -inline typename std::enable_if<I >= type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &,
921   - T &) {
  1205 +template <class AssignTo, class ConvertTo, std::size_t I>
  1206 +inline typename std::enable_if<(I >= type_count_base<AssignTo>::value), bool>::type
  1207 +tuple_conversion(const std::vector<std::string> &, AssignTo &) {
922 1208 return true;
923 1209 }
  1210 +
  1211 +/// Conversion of a tuple element where the type size ==1 and not a mutable container
  1212 +template <class AssignTo, class ConvertTo>
  1213 +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && type_count<ConvertTo>::value == 1, bool>::type
  1214 +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
  1215 + auto retval = lexical_assign<AssignTo, ConvertTo>(strings[0], output);
  1216 + strings.erase(strings.begin());
  1217 + return retval;
  1218 +}
  1219 +
  1220 +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container
  1221 +template <class AssignTo, class ConvertTo>
  1222 +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && (type_count<ConvertTo>::value > 1) &&
  1223 + type_count<ConvertTo>::value == type_count_min<ConvertTo>::value,
  1224 + bool>::type
  1225 +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
  1226 + auto retval = lexical_conversion<AssignTo, ConvertTo>(strings, output);
  1227 + strings.erase(strings.begin(), strings.begin() + type_count<ConvertTo>::value);
  1228 + return retval;
  1229 +}
  1230 +
  1231 +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes
  1232 +template <class AssignTo, class ConvertTo>
  1233 +inline typename std::enable_if<is_mutable_container<ConvertTo>::value ||
  1234 + type_count<ConvertTo>::value != type_count_min<ConvertTo>::value,
  1235 + bool>::type
  1236 +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
  1237 +
  1238 + std::size_t index{subtype_count_min<ConvertTo>::value};
  1239 + const std::size_t mx_count{subtype_count<ConvertTo>::value};
  1240 + const std::size_t mx{(std::max)(mx_count, strings.size())};
  1241 +
  1242 + while(index < mx) {
  1243 + if(is_separator(strings[index])) {
  1244 + break;
  1245 + }
  1246 + ++index;
  1247 + }
  1248 + bool retval = lexical_conversion<AssignTo, ConvertTo>(
  1249 + std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output);
  1250 + strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1);
  1251 + return retval;
  1252 +}
  1253 +
924 1254 /// Tuple conversion operation
925   -template <class T, class XC, std::size_t I>
926   - inline typename std::enable_if <
927   - I<type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &strings, T &output) {
  1255 +template <class AssignTo, class ConvertTo, std::size_t I>
  1256 +inline typename std::enable_if<(I < type_count_base<AssignTo>::value), bool>::type
  1257 +tuple_conversion(std::vector<std::string> strings, AssignTo &output) {
928 1258 bool retval = true;
929   - if(strings.size() > I) {
930   - retval = retval && lexical_assign<typename std::tuple_element<I, T>::type,
931   - typename std::conditional<is_tuple_like<XC>::value,
932   - typename std::tuple_element<I, XC>::type,
933   - XC>::type>(strings[I], std::get<I>(output));
  1259 + using ConvertToElement = typename std::
  1260 + conditional<is_tuple_like<ConvertTo>::value, typename std::tuple_element<I, ConvertTo>::type, ConvertTo>::type;
  1261 + if(!strings.empty()) {
  1262 + retval = retval && tuple_type_conversion<typename std::tuple_element<I, AssignTo>::type, ConvertToElement>(
  1263 + strings, std::get<I>(output));
934 1264 }
935   - retval = retval && tuple_conversion<T, XC, I + 1>(strings, output);
  1265 + retval = retval && tuple_conversion<AssignTo, ConvertTo, I + 1>(std::move(strings), output);
936 1266 return retval;
937 1267 }
938 1268  
939   -/// Conversion for tuples
940   -template <class T, class XC, enable_if_t<is_tuple_like<T>::value, detail::enabler> = detail::dummy>
941   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  1269 +/// Lexical conversion of a container types with tuple elements of size 2
  1270 +template <class AssignTo,
  1271 + class ConvertTo,
  1272 + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
  1273 + type_count_base<ConvertTo>::value == 2,
  1274 + detail::enabler>>
  1275 +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output) {
  1276 + output.clear();
  1277 + while(!strings.empty()) {
  1278 +
  1279 + typename std::remove_const<typename std::tuple_element<0, typename ConvertTo::value_type>::type>::type v1;
  1280 + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2;
  1281 + bool retval = tuple_type_conversion<decltype(v1), decltype(v1)>(strings, v1);
  1282 + if(!strings.empty()) {
  1283 + retval = retval && tuple_type_conversion<decltype(v2), decltype(v2)>(strings, v2);
  1284 + }
  1285 + if(retval) {
  1286 + output.insert(output.end(), typename AssignTo::value_type{v1, v2});
  1287 + } else {
  1288 + return false;
  1289 + }
  1290 + }
  1291 + return (!output.empty());
  1292 +}
  1293 +
  1294 +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2
  1295 +template <class AssignTo,
  1296 + class ConvertTo,
  1297 + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value &&
  1298 + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value ||
  1299 + type_count<ConvertTo>::value > 2),
  1300 + detail::enabler>>
  1301 +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
942 1302 static_assert(
943   - !is_tuple_like<XC>::value || type_count<T>::value == type_count<XC>::value,
  1303 + !is_tuple_like<ConvertTo>::value || type_count_base<AssignTo>::value == type_count_base<ConvertTo>::value,
944 1304 "if the conversion type is defined as a tuple it must be the same size as the type you are converting to");
945   - return tuple_conversion<T, XC, 0>(strings, output);
  1305 + return tuple_conversion<AssignTo, ConvertTo, 0>(strings, output);
946 1306 }
947 1307  
948   -/// Lexical conversion of a vector types with type_size >2
949   -template <class T,
950   - class XC,
951   - enable_if_t<expected_count<T>::value == expected_max_vector_size &&
952   - expected_count<XC>::value == expected_max_vector_size && (type_count<XC>::value > 2),
953   - detail::enabler> = detail::dummy>
954   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  1308 +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1
  1309 +template <class AssignTo,
  1310 + class ConvertTo,
  1311 + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
  1312 + type_count_base<ConvertTo>::value != 2 &&
  1313 + ((type_count<ConvertTo>::value > 2) ||
  1314 + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)),
  1315 + detail::enabler>>
  1316 +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
955 1317 bool retval = true;
956 1318 output.clear();
957 1319 std::vector<std::string> temp;
958   - std::size_t ii = 0;
959   - std::size_t icount = 0;
960   - std::size_t xcm = type_count<XC>::value;
961   - while(ii < strings.size()) {
  1320 + std::size_t ii{0};
  1321 + std::size_t icount{0};
  1322 + std::size_t xcm{type_count<ConvertTo>::value};
  1323 + auto ii_max = strings.size();
  1324 + while(ii < ii_max) {
962 1325 temp.push_back(strings[ii]);
963 1326 ++ii;
964 1327 ++icount;
965   - if(icount == xcm || temp.back().empty()) {
966   - if(static_cast<int>(xcm) == expected_max_vector_size) {
  1328 + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) {
  1329 + if(static_cast<int>(xcm) > type_count_min<ConvertTo>::value && is_separator(temp.back())) {
967 1330 temp.pop_back();
968 1331 }
969   - output.emplace_back();
970   - retval = retval && lexical_conversion<typename T::value_type, typename XC::value_type>(temp, output.back());
  1332 + typename AssignTo::value_type temp_out;
  1333 + retval = retval &&
  1334 + lexical_conversion<typename AssignTo::value_type, typename ConvertTo::value_type>(temp, temp_out);
971 1335 temp.clear();
972 1336 if(!retval) {
973 1337 return false;
974 1338 }
  1339 + output.insert(output.end(), std::move(temp_out));
975 1340 icount = 0;
976 1341 }
977 1342 }
978 1343 return retval;
979 1344 }
  1345 +
  1346 +/// conversion for wrapper types
  1347 +template <
  1348 + typename AssignTo,
  1349 + class ConvertTo,
  1350 + enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value, detail::enabler> = detail::dummy>
  1351 +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
  1352 + if(strings.empty() || strings.front().empty()) {
  1353 + output = ConvertTo{};
  1354 + return true;
  1355 + }
  1356 + typename ConvertTo::value_type val;
  1357 + if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) {
  1358 + output = ConvertTo{val};
  1359 + return true;
  1360 + }
  1361 + return false;
  1362 +}
  1363 +
980 1364 /// Sum a vector of flag representations
981 1365 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
982 1366 /// by
983 1367 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
984 1368 /// common true and false strings then uses stoll to convert the rest for summing
985   -template <typename T,
986   - enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
  1369 +template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
987 1370 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
988 1371 std::int64_t count{0};
989 1372 for(auto &flag : flags) {
... ... @@ -997,8 +1380,7 @@ void sum_flag_vector(const std::vector&lt;std::string&gt; &amp;flags, T &amp;output) {
997 1380 /// by
998 1381 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
999 1382 /// common true and false strings then uses stoll to convert the rest for summing
1000   -template <typename T,
1001   - enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy>
  1383 +template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy>
1002 1384 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
1003 1385 std::int64_t count{0};
1004 1386 for(auto &flag : flags) {
... ...
tests/AppTest.cpp
... ... @@ -454,98 +454,6 @@ TEST_F(TApp, SepInt) {
454 454 EXPECT_EQ(i, 4);
455 455 }
456 456  
457   -TEST_F(TApp, OneStringAgain) {
458   - std::string str;
459   - app.add_option("-s,--string", str);
460   - args = {"--string", "mystring"};
461   - run();
462   - EXPECT_EQ(1u, app.count("-s"));
463   - EXPECT_EQ(1u, app.count("--string"));
464   - EXPECT_EQ(str, "mystring");
465   -}
466   -
467   -TEST_F(TApp, OneStringFunction) {
468   - std::string str;
469   - app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; });
470   - args = {"--string", "mystring"};
471   - run();
472   - EXPECT_EQ(1u, app.count("-s"));
473   - EXPECT_EQ(1u, app.count("--string"));
474   - EXPECT_EQ(str, "mystring");
475   -}
476   -
477   -TEST_F(TApp, doubleFunction) {
478   - double res{0.0};
479   - app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
480   - args = {"--val", "-354.356"};
481   - run();
482   - EXPECT_EQ(res, 300.356);
483   - // get the original value as entered as an integer
484   - EXPECT_EQ(app["--val"]->as<float>(), -354.356f);
485   -}
486   -
487   -TEST_F(TApp, doubleFunctionFail) {
488   - double res;
489   - app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
490   - args = {"--val", "not_double"};
491   - EXPECT_THROW(run(), CLI::ConversionError);
492   -}
493   -
494   -TEST_F(TApp, doubleVectorFunction) {
495   - std::vector<double> res;
496   - app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
497   - res = val;
498   - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
499   - });
500   - args = {"--val", "5", "--val", "6", "--val", "7"};
501   - run();
502   - EXPECT_EQ(res.size(), 3u);
503   - EXPECT_EQ(res[0], 10.0);
504   - EXPECT_EQ(res[2], 12.0);
505   -}
506   -
507   -TEST_F(TApp, doubleVectorFunctionFail) {
508   - std::vector<double> res;
509   - std::string vstring = "--val";
510   - app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
511   - res = val;
512   - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
513   - });
514   - args = {"--val", "five", "--val", "nine", "--val", "7"};
515   - EXPECT_THROW(run(), CLI::ConversionError);
516   - // check that getting the results through the results function generates the same error
517   - EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError);
518   - auto strvec = app[vstring]->as<std::vector<std::string>>();
519   - EXPECT_EQ(strvec.size(), 3u);
520   -}
521   -
522   -TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) {
523   - std::vector<double> res;
524   - auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
525   - res = val;
526   - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
527   - });
528   - args = {"--val", "5", "--val", "6", "--val", "7"};
529   - run();
530   - EXPECT_EQ(res.size(), 3u);
531   - EXPECT_EQ(res[0], 10.0);
532   - EXPECT_EQ(res[2], 12.0);
533   - EXPECT_FALSE(opt->get_run_callback_for_default());
534   - opt->run_callback_for_default();
535   - opt->default_val(std::vector<int>{2, 1, -2});
536   - EXPECT_EQ(res[0], 7.0);
537   - EXPECT_EQ(res[2], 3.0);
538   -
539   - EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError);
540   - auto vec = opt->as<std::vector<double>>();
541   - ASSERT_EQ(vec.size(), 3U);
542   - EXPECT_EQ(vec[0], 5.0);
543   - EXPECT_EQ(vec[2], 7.0);
544   - opt->check(CLI::Number);
545   - opt->run_callback_for_default(false);
546   - EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError);
547   -}
548   -
549 457 TEST_F(TApp, DefaultStringAgain) {
550 458 std::string str = "previous";
551 459 app.add_option("-s,--string", str);
... ... @@ -647,35 +555,6 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) {
647 555 EXPECT_EQ(1u, app.count("-A"));
648 556 }
649 557  
650   -TEST_F(TApp, BoolAndIntFlags) {
651   -
652   - bool bflag{false};
653   - int iflag{0};
654   - unsigned int uflag{0};
655   -
656   - app.add_flag("-b", bflag);
657   - app.add_flag("-i", iflag);
658   - app.add_flag("-u", uflag);
659   -
660   - args = {"-b", "-i", "-u"};
661   - run();
662   - EXPECT_TRUE(bflag);
663   - EXPECT_EQ(1, iflag);
664   - EXPECT_EQ((unsigned int)1, uflag);
665   -
666   - args = {"-b", "-b"};
667   - ASSERT_NO_THROW(run());
668   - EXPECT_TRUE(bflag);
669   -
670   - bflag = false;
671   -
672   - args = {"-iiiuu"};
673   - run();
674   - EXPECT_FALSE(bflag);
675   - EXPECT_EQ(3, iflag);
676   - EXPECT_EQ((unsigned int)2, uflag);
677   -}
678   -
679 558 TEST_F(TApp, FlagLikeOption) {
680 559 bool val{false};
681 560 auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
... ... @@ -724,32 +603,6 @@ TEST_F(TApp, BoolOnlyFlag) {
724 603 EXPECT_THROW(run(), CLI::ArgumentMismatch);
725 604 }
726 605  
727   -TEST_F(TApp, BoolOption) {
728   - bool bflag{false};
729   - app.add_option("-b", bflag);
730   -
731   - args = {"-b", "false"};
732   - run();
733   - EXPECT_FALSE(bflag);
734   -
735   - args = {"-b", "1"};
736   - run();
737   - EXPECT_TRUE(bflag);
738   -
739   - args = {"-b", "-7"};
740   - run();
741   - EXPECT_FALSE(bflag);
742   -
743   - // cause an out of bounds error internally
744   - args = {"-b", "751615654161688126132138844896646748852"};
745   - run();
746   - EXPECT_TRUE(bflag);
747   -
748   - args = {"-b", "-751615654161688126132138844896646748852"};
749   - run();
750   - EXPECT_FALSE(bflag);
751   -}
752   -
753 606 TEST_F(TApp, ShortOpts) {
754 607  
755 608 unsigned long long funnyint{0};
... ... @@ -892,37 +745,6 @@ TEST_F(TApp, TakeLastOptMulti) {
892 745 EXPECT_EQ(vals, std::vector<int>({2, 3}));
893 746 }
894 747  
895   -TEST_F(TApp, vectorDefaults) {
896   - std::vector<int> vals{4, 5};
897   - auto opt = app.add_option("--long", vals, "", true);
898   -
899   - args = {"--long", "[1,2,3]"};
900   -
901   - run();
902   -
903   - EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
904   -
905   - args.clear();
906   - run();
907   - auto res = app["--long"]->as<std::vector<int>>();
908   - EXPECT_EQ(res, std::vector<int>({4, 5}));
909   -
910   - app.clear();
911   - opt->expected(1)->take_last();
912   - res = app["--long"]->as<std::vector<int>>();
913   - EXPECT_EQ(res, std::vector<int>({5}));
914   - opt->take_first();
915   - res = app["--long"]->as<std::vector<int>>();
916   - EXPECT_EQ(res, std::vector<int>({4}));
917   -
918   - opt->expected(0, 1)->take_last();
919   - run();
920   -
921   - EXPECT_EQ(res, std::vector<int>({4}));
922   - res = app["--long"]->as<std::vector<int>>();
923   - EXPECT_EQ(res, std::vector<int>({5}));
924   -}
925   -
926 748 TEST_F(TApp, TakeLastOptMulti_alternative_path) {
927 749 std::vector<int> vals;
928 750 app.add_option("--long", vals)->expected(2, -1)->take_last();
... ... @@ -1452,27 +1274,6 @@ TEST_F(TApp, CallbackFlags) {
1452 1274 EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
1453 1275 }
1454 1276  
1455   -TEST_F(TApp, CallbackBoolFlags) {
1456   -
1457   - bool value{false};
1458   -
1459   - auto func = [&value]() { value = true; };
1460   -
1461   - auto cback = app.add_flag_callback("--val", func);
1462   - args = {"--val"};
1463   - run();
1464   - EXPECT_TRUE(value);
1465   - value = false;
1466   - args = {"--val=false"};
1467   - run();
1468   - EXPECT_FALSE(value);
1469   -
1470   - EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
1471   - cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
1472   - args = {"--val", "--val=false"};
1473   - EXPECT_THROW(run(), CLI::ArgumentMismatch);
1474   -}
1475   -
1476 1277 TEST_F(TApp, CallbackFlagsFalse) {
1477 1278 std::int64_t value = 0;
1478 1279  
... ... @@ -1747,107 +1548,6 @@ TEST_F(TApp, NotFileExists) {
1747 1548 EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
1748 1549 }
1749 1550  
1750   -TEST_F(TApp, pair_check) {
1751   - std::string myfile{"pair_check_file.txt"};
1752   - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
1753   - EXPECT_TRUE(ok);
1754   -
1755   - EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
1756   - std::pair<std::string, int> findex;
1757   -
1758   - auto v0 = CLI::ExistingFile;
1759   - v0.application_index(0);
1760   - auto v1 = CLI::PositiveNumber;
1761   - v1.application_index(1);
1762   - app.add_option("--file", findex)->check(v0)->check(v1);
1763   -
1764   - args = {"--file", myfile, "2"};
1765   -
1766   - EXPECT_NO_THROW(run());
1767   -
1768   - EXPECT_EQ(findex.first, myfile);
1769   - EXPECT_EQ(findex.second, 2);
1770   -
1771   - args = {"--file", myfile, "-3"};
1772   -
1773   - EXPECT_THROW(run(), CLI::ValidationError);
1774   -
1775   - args = {"--file", myfile, "2"};
1776   - std::remove(myfile.c_str());
1777   - EXPECT_THROW(run(), CLI::ValidationError);
1778   -}
1779   -
1780   -// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
1781   -
1782   -TEST_F(TApp, pair_check_take_first) {
1783   - std::string myfile{"pair_check_file2.txt"};
1784   - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
1785   - EXPECT_TRUE(ok);
1786   -
1787   - EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
1788   - std::pair<std::string, int> findex;
1789   -
1790   - auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
1791   - EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound);
1792   - opt->get_validator(0)->application_index(0);
1793   - opt->get_validator(1)->application_index(1);
1794   - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
1795   - args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"};
1796   - // should only check the last one
1797   - EXPECT_NO_THROW(run());
1798   -
1799   - EXPECT_EQ(findex.first, myfile);
1800   - EXPECT_EQ(findex.second, 2);
1801   -
1802   - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
1803   -
1804   - EXPECT_THROW(run(), CLI::ValidationError);
1805   -}
1806   -
1807   -TEST_F(TApp, VectorFixedString) {
1808   - std::vector<std::string> strvec;
1809   - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
1810   -
1811   - CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3);
1812   - EXPECT_EQ(3, opt->get_expected());
1813   -
1814   - args = {"--string", "mystring", "mystring2", "mystring3"};
1815   - run();
1816   - EXPECT_EQ(3u, app.count("--string"));
1817   - EXPECT_EQ(answer, strvec);
1818   -}
1819   -
1820   -TEST_F(TApp, VectorDefaultedFixedString) {
1821   - std::vector<std::string> strvec{"one"};
1822   - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
1823   -
1824   - CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str();
1825   - EXPECT_EQ(3, opt->get_expected());
1826   -
1827   - args = {"--string", "mystring", "mystring2", "mystring3"};
1828   - run();
1829   - EXPECT_EQ(3u, app.count("--string"));
1830   - EXPECT_EQ(answer, strvec);
1831   -}
1832   -
1833   -TEST_F(TApp, VectorIndexedValidator) {
1834   - std::vector<int> vvec;
1835   -
1836   - CLI::Option *opt = app.add_option("-v", vvec);
1837   -
1838   - args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
1839   - run();
1840   - EXPECT_EQ(4u, app.count("-v"));
1841   - EXPECT_EQ(4u, vvec.size());
1842   - opt->check(CLI::PositiveNumber.application_index(0));
1843   - opt->check((!CLI::PositiveNumber).application_index(1));
1844   - EXPECT_NO_THROW(run());
1845   - EXPECT_EQ(4u, vvec.size());
1846   - // v[3] would be negative
1847   - opt->check(CLI::PositiveNumber.application_index(3));
1848   - EXPECT_THROW(run(), CLI::ValidationError);
1849   -}
1850   -
1851 1551 TEST_F(TApp, DefaultedResult) {
1852 1552 std::string sval = "NA";
1853 1553 int ival{0};
... ... @@ -1866,93 +1566,6 @@ TEST_F(TApp, DefaultedResult) {
1866 1566 EXPECT_EQ(newIval, 442);
1867 1567 }
1868 1568  
1869   -TEST_F(TApp, VectorUnlimString) {
1870   - std::vector<std::string> strvec;
1871   - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
1872   -
1873   - CLI::Option *opt = app.add_option("-s,--string", strvec);
1874   - EXPECT_EQ(1, opt->get_expected());
1875   - EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max());
1876   -
1877   - args = {"--string", "mystring", "mystring2", "mystring3"};
1878   - run();
1879   - EXPECT_EQ(3u, app.count("--string"));
1880   - EXPECT_EQ(answer, strvec);
1881   -
1882   - args = {"-s", "mystring", "mystring2", "mystring3"};
1883   - run();
1884   - EXPECT_EQ(3u, app.count("--string"));
1885   - EXPECT_EQ(answer, strvec);
1886   -}
1887   -
1888   -// From https://github.com/CLIUtils/CLI11/issues/420
1889   -TEST_F(TApp, stringLikeTests) {
1890   - struct nType {
1891   - explicit nType(const std::string &a_value) : m_value{a_value} {}
1892   -
1893   - explicit operator std::string() const { return std::string{"op str"}; }
1894   -
1895   - std::string m_value;
1896   - };
1897   -
1898   - nType m_type{"abc"};
1899   - app.add_option("--type", m_type, "type")->capture_default_str();
1900   - run();
1901   -
1902   - EXPECT_EQ(app["--type"]->as<std::string>(), "op str");
1903   - args = {"--type", "bca"};
1904   - run();
1905   - EXPECT_EQ(std::string(m_type), "op str");
1906   - EXPECT_EQ(m_type.m_value, "bca");
1907   -}
1908   -
1909   -TEST_F(TApp, VectorExpectedRange) {
1910   - std::vector<std::string> strvec;
1911   -
1912   - CLI::Option *opt = app.add_option("--string", strvec);
1913   - opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
1914   -
1915   - args = {"--string", "mystring", "mystring2", "mystring3"};
1916   - run();
1917   - EXPECT_EQ(3u, app.count("--string"));
1918   -
1919   - args = {"--string", "mystring"};
1920   - EXPECT_THROW(run(), CLI::ArgumentMismatch);
1921   -
1922   - args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"};
1923   - EXPECT_THROW(run(), CLI::ArgumentMismatch);
1924   -
1925   - EXPECT_EQ(opt->get_expected_max(), 4);
1926   - EXPECT_EQ(opt->get_expected_min(), 2);
1927   - opt->expected(4, 2); // just test the handling of reversed arguments
1928   - EXPECT_EQ(opt->get_expected_max(), 4);
1929   - EXPECT_EQ(opt->get_expected_min(), 2);
1930   - opt->expected(-5);
1931   - EXPECT_EQ(opt->get_expected_max(), 5);
1932   - EXPECT_EQ(opt->get_expected_min(), 5);
1933   - opt->expected(-5, 7);
1934   - EXPECT_EQ(opt->get_expected_max(), 7);
1935   - EXPECT_EQ(opt->get_expected_min(), 5);
1936   -}
1937   -
1938   -TEST_F(TApp, VectorFancyOpts) {
1939   - std::vector<std::string> strvec;
1940   - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
1941   -
1942   - CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3);
1943   - EXPECT_EQ(3, opt->get_expected());
1944   -
1945   - args = {"--string", "mystring", "mystring2", "mystring3"};
1946   - run();
1947   - EXPECT_EQ(3u, app.count("--string"));
1948   - EXPECT_EQ(answer, strvec);
1949   -
1950   - args = {"one", "two"};
1951   - EXPECT_THROW(run(), CLI::RequiredError);
1952   -
1953   - EXPECT_THROW(run(), CLI::ParseError);
1954   -}
1955   -
1956 1569 TEST_F(TApp, OriginalOrder) {
1957 1570 std::vector<int> st1;
1958 1571 CLI::Option *op1 = app.add_option("-a", st1);
... ... @@ -2406,170 +2019,6 @@ TEST_F(TApp, EachItem) {
2406 2019 EXPECT_EQ(results, dummy);
2407 2020 }
2408 2021  
2409   -// #87
2410   -TEST_F(TApp, CustomDoubleOption) {
2411   -
2412   - std::pair<int, double> custom_opt;
2413   -
2414   - auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
2415   - custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
2416   - return true;
2417   - });
2418   - opt->type_name("INT FLOAT")->type_size(2);
2419   -
2420   - args = {"12", "1.5"};
2421   -
2422   - run();
2423   - EXPECT_EQ(custom_opt.first, 12);
2424   - EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
2425   -}
2426   -
2427   -// now with tuple support this is possible
2428   -TEST_F(TApp, CustomDoubleOptionAlt) {
2429   -
2430   - std::pair<int, double> custom_opt;
2431   -
2432   - app.add_option("posit", custom_opt);
2433   -
2434   - args = {"12", "1.5"};
2435   -
2436   - run();
2437   - EXPECT_EQ(custom_opt.first, 12);
2438   - EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
2439   -}
2440   -
2441   -// now with independent type sizes and expected this is possible
2442   -TEST_F(TApp, vectorPair) {
2443   -
2444   - std::vector<std::pair<int, std::string>> custom_opt;
2445   -
2446   - auto opt = app.add_option("--dict", custom_opt);
2447   -
2448   - args = {"--dict", "1", "str1", "--dict", "3", "str3"};
2449   -
2450   - run();
2451   - EXPECT_EQ(custom_opt.size(), 2u);
2452   - EXPECT_EQ(custom_opt[0].first, 1);
2453   - EXPECT_EQ(custom_opt[1].second, "str3");
2454   -
2455   - args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"};
2456   - run();
2457   - EXPECT_EQ(custom_opt.size(), 3u);
2458   - EXPECT_EQ(custom_opt[2].first, -1);
2459   - EXPECT_EQ(custom_opt[2].second, "str4");
2460   - opt->check(CLI::PositiveNumber.application_index(0));
2461   -
2462   - EXPECT_THROW(run(), CLI::ValidationError);
2463   -}
2464   -
2465   -TEST_F(TApp, vectorPairFail) {
2466   -
2467   - std::vector<std::pair<int, std::string>> custom_opt;
2468   -
2469   - app.add_option("--dict", custom_opt);
2470   -
2471   - args = {"--dict", "1", "str1", "--dict", "str3", "1"};
2472   -
2473   - EXPECT_THROW(run(), CLI::ConversionError);
2474   -}
2475   -
2476   -TEST_F(TApp, vectorPairTypeRange) {
2477   -
2478   - std::vector<std::pair<int, std::string>> custom_opt;
2479   -
2480   - auto opt = app.add_option("--dict", custom_opt);
2481   -
2482   - opt->type_size(2, 1); // just test switched arguments
2483   - EXPECT_EQ(opt->get_type_size_min(), 1);
2484   - EXPECT_EQ(opt->get_type_size_max(), 2);
2485   -
2486   - args = {"--dict", "1", "str1", "--dict", "3", "str3"};
2487   -
2488   - run();
2489   - EXPECT_EQ(custom_opt.size(), 2u);
2490   - EXPECT_EQ(custom_opt[0].first, 1);
2491   - EXPECT_EQ(custom_opt[1].second, "str3");
2492   -
2493   - args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
2494   - run();
2495   - EXPECT_EQ(custom_opt.size(), 3u);
2496   - EXPECT_TRUE(custom_opt[1].second.empty());
2497   - EXPECT_EQ(custom_opt[2].first, -1);
2498   - EXPECT_EQ(custom_opt[2].second, "str4");
2499   -
2500   - opt->type_size(-2, -1); // test negative arguments
2501   - EXPECT_EQ(opt->get_type_size_min(), 1);
2502   - EXPECT_EQ(opt->get_type_size_max(), 2);
2503   - // this type size spec should run exactly as before
2504   - run();
2505   - EXPECT_EQ(custom_opt.size(), 3u);
2506   - EXPECT_TRUE(custom_opt[1].second.empty());
2507   - EXPECT_EQ(custom_opt[2].first, -1);
2508   - EXPECT_EQ(custom_opt[2].second, "str4");
2509   -}
2510   -
2511   -// now with independent type sizes and expected this is possible
2512   -TEST_F(TApp, vectorTuple) {
2513   -
2514   - std::vector<std::tuple<int, std::string, double>> custom_opt;
2515   -
2516   - auto opt = app.add_option("--dict", custom_opt);
2517   -
2518   - args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
2519   -
2520   - run();
2521   - EXPECT_EQ(custom_opt.size(), 2u);
2522   - EXPECT_EQ(std::get<0>(custom_opt[0]), 1);
2523   - EXPECT_EQ(std::get<1>(custom_opt[1]), "str3");
2524   - EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7);
2525   -
2526   - args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
2527   - run();
2528   - EXPECT_EQ(custom_opt.size(), 3u);
2529   - EXPECT_EQ(std::get<0>(custom_opt[2]), -1);
2530   - EXPECT_EQ(std::get<1>(custom_opt[2]), "str4");
2531   - EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87);
2532   - opt->check(CLI::PositiveNumber.application_index(0));
2533   -
2534   - EXPECT_THROW(run(), CLI::ValidationError);
2535   -
2536   - args.back() = "haha";
2537   - args[9] = "45";
2538   - EXPECT_THROW(run(), CLI::ConversionError);
2539   -}
2540   -
2541   -// now with independent type sizes and expected this is possible
2542   -TEST_F(TApp, vectorVector) {
2543   -
2544   - std::vector<std::vector<int>> custom_opt;
2545   -
2546   - auto opt = app.add_option("--dict", custom_opt);
2547   -
2548   - args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
2549   -
2550   - run();
2551   - EXPECT_EQ(custom_opt.size(), 2u);
2552   - EXPECT_EQ(custom_opt[0].size(), 3u);
2553   - EXPECT_EQ(custom_opt[1].size(), 2u);
2554   -
2555   - args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
2556   - "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
2557   - run();
2558   - EXPECT_EQ(custom_opt.size(), 4u);
2559   - EXPECT_EQ(custom_opt[0].size(), 3u);
2560   - EXPECT_EQ(custom_opt[1].size(), 2u);
2561   - EXPECT_EQ(custom_opt[2].size(), 1u);
2562   - EXPECT_EQ(custom_opt[3].size(), 10u);
2563   - opt->check(CLI::PositiveNumber.application_index(9));
2564   -
2565   - EXPECT_THROW(run(), CLI::ValidationError);
2566   - args.pop_back();
2567   - EXPECT_NO_THROW(run());
2568   -
2569   - args.back() = "haha";
2570   - EXPECT_THROW(run(), CLI::ConversionError);
2571   -}
2572   -
2573 2022 // #128
2574 2023 TEST_F(TApp, RepeatingMultiArgumentOptions) {
2575 2024 std::vector<std::string> entries;
... ...
tests/BoostOptionTypeTest.cpp 0 β†’ 100644
  1 +#include "app_helper.hpp"
  2 +#include <boost/container/flat_map.hpp>
  3 +#include <boost/container/flat_set.hpp>
  4 +#include <boost/container/slist.hpp>
  5 +#include <boost/container/small_vector.hpp>
  6 +#include <boost/container/stable_vector.hpp>
  7 +#include <boost/container/static_vector.hpp>
  8 +#include <boost/container/vector.hpp>
  9 +#include <string>
  10 +#include <vector>
  11 +
  12 +#include "gmock/gmock.h"
  13 +
  14 +using namespace boost::container;
  15 +
  16 +template <class T> class TApp_container_single_boost : public TApp {
  17 + public:
  18 + using container_type = T;
  19 + container_type cval{};
  20 + TApp_container_single_boost() : TApp(){};
  21 +};
  22 +
  23 +using containerTypes_single_boost =
  24 + ::testing::Types<small_vector<int, 2>, small_vector<int, 3>, flat_set<int>, stable_vector<int>, slist<int>>;
  25 +
  26 +TYPED_TEST_SUITE(TApp_container_single_boost, containerTypes_single_boost, );
  27 +
  28 +TYPED_TEST(TApp_container_single_boost, containerInt_boost) {
  29 +
  30 + auto &cv = TApp_container_single_boost<TypeParam>::cval;
  31 + CLI::Option *opt = (TApp::app).add_option("-v", cv);
  32 +
  33 + TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
  34 + TApp::run();
  35 + EXPECT_EQ(4u, (TApp::app).count("-v"));
  36 + EXPECT_EQ(4u, cv.size());
  37 + opt->check(CLI::PositiveNumber.application_index(0));
  38 + opt->check((!CLI::PositiveNumber).application_index(1));
  39 + EXPECT_NO_THROW(TApp::run());
  40 + EXPECT_EQ(4u, cv.size());
  41 + // v[3] would be negative
  42 + opt->check(CLI::PositiveNumber.application_index(3));
  43 + EXPECT_THROW(TApp::run(), CLI::ValidationError);
  44 +}
  45 +
  46 +template <class T> class TApp_container_pair_boost : public TApp {
  47 + public:
  48 + using container_type = T;
  49 + container_type cval{};
  50 + TApp_container_pair_boost() : TApp(){};
  51 +};
  52 +
  53 +using isp = std::pair<int, std::string>;
  54 +using containerTypes_pair_boost = ::testing::
  55 + Types<stable_vector<isp>, small_vector<isp, 2>, flat_set<isp>, slist<isp>, vector<isp>, flat_map<int, std::string>>;
  56 +
  57 +TYPED_TEST_SUITE(TApp_container_pair_boost, containerTypes_pair_boost, );
  58 +
  59 +TYPED_TEST(TApp_container_pair_boost, containerPair_boost) {
  60 +
  61 + auto &cv = TApp_container_pair_boost<TypeParam>::cval;
  62 + (TApp::app).add_option("--dict", cv);
  63 +
  64 + TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"};
  65 +
  66 + TApp::run();
  67 + EXPECT_EQ(cv.size(), 2u);
  68 +
  69 + TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
  70 + TApp::run();
  71 + EXPECT_EQ(cv.size(), 3u);
  72 +}
  73 +
  74 +template <class T> class TApp_container_tuple_boost : public TApp {
  75 + public:
  76 + using container_type = T;
  77 + container_type cval{};
  78 + TApp_container_tuple_boost() : TApp(){};
  79 +};
  80 +
  81 +using tup_obj = std::tuple<int, std::string, double>;
  82 +using containerTypes_tuple_boost =
  83 + ::testing::Types<small_vector<tup_obj, 3>, stable_vector<tup_obj>, flat_set<tup_obj>, slist<tup_obj>>;
  84 +
  85 +TYPED_TEST_SUITE(TApp_container_tuple_boost, containerTypes_tuple_boost, );
  86 +
  87 +TYPED_TEST(TApp_container_tuple_boost, containerTuple_boost) {
  88 +
  89 + auto &cv = TApp_container_tuple_boost<TypeParam>::cval;
  90 + (TApp::app).add_option("--dict", cv);
  91 +
  92 + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
  93 +
  94 + TApp::run();
  95 + EXPECT_EQ(cv.size(), 2u);
  96 +
  97 + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
  98 + TApp::run();
  99 + EXPECT_EQ(cv.size(), 3u);
  100 +}
  101 +
  102 +using icontainer1 = vector<int>;
  103 +using icontainer2 = flat_set<int>;
  104 +using icontainer3 = slist<int>;
  105 +using containerTypes_container_boost = ::testing::Types<std::vector<icontainer1>,
  106 + slist<icontainer1>,
  107 + flat_set<icontainer1>,
  108 + small_vector<icontainer1, 2>,
  109 + std::vector<icontainer2>,
  110 + slist<icontainer2>,
  111 + flat_set<icontainer2>,
  112 + stable_vector<icontainer2>,
  113 + static_vector<icontainer3, 10>,
  114 + slist<icontainer3>,
  115 + flat_set<icontainer3>,
  116 + static_vector<icontainer3, 10>>;
  117 +
  118 +template <class T> class TApp_container_container_boost : public TApp {
  119 + public:
  120 + using container_type = T;
  121 + container_type cval{};
  122 + TApp_container_container_boost() : TApp(){};
  123 +};
  124 +
  125 +TYPED_TEST_SUITE(TApp_container_container_boost, containerTypes_container_boost, );
  126 +
  127 +TYPED_TEST(TApp_container_container_boost, containerContainer_boost) {
  128 +
  129 + auto &cv = TApp_container_container_boost<TypeParam>::cval;
  130 + (TApp::app).add_option("--dict", cv);
  131 +
  132 + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
  133 +
  134 + TApp::run();
  135 + EXPECT_EQ(cv.size(), 2u);
  136 +
  137 + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
  138 + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
  139 + TApp::run();
  140 + EXPECT_EQ(cv.size(), 4u);
  141 +}
... ...
tests/CMakeLists.txt
... ... @@ -32,13 +32,19 @@ endif()
32 32 set(GOOGLE_TEST_INDIVIDUAL OFF)
33 33 include(AddGoogletest)
34 34  
  35 +# Add boost to test boost::optional if available
  36 +find_package(Boost 1.61)
  37 +
  38 +set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>)
  39 +
35 40 set(CLI11_TESTS
36 41 HelpersTest
37 42 ConfigFileTest
  43 + OptionTypeTest
38 44 SimpleTest
39 45 AppTest
40 46 SetTest
41   - TransformTest
  47 + TransformTest
42 48 CreationTest
43 49 SubcommandTest
44 50 HelpTest
... ... @@ -47,6 +53,7 @@ set(CLI11_TESTS
47 53 OptionalTest
48 54 DeprecatedTest
49 55 StringParseTest
  56 + ComplexTypeTest
50 57 TrueFalseTest
51 58 OptionGroupTest
52 59 )
... ... @@ -55,6 +62,10 @@ if(WIN32)
55 62 list(APPEND CLI11_TESTS WindowsTest)
56 63 endif()
57 64  
  65 +if (Boost_FOUND)
  66 + list(APPEND CLI11_TESTS BoostOptionTypeTest)
  67 +endif()
  68 +
58 69 set(CLI11_MULTIONLY_TESTS TimerTest)
59 70  
60 71 # Only affects current directory, so safe
... ... @@ -140,20 +151,29 @@ file(WRITE &quot;${PROJECT_BINARY_DIR}/CTestCustom.cmake&quot;
140 151 "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")"
141 152 )
142 153  
143   -# Add boost to test boost::optional if available
144   -find_package(Boost 1.61)
145   -
146   -set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>)
147   -
148 154 target_compile_definitions(informational PRIVATE ${boost-optional-def})
149 155 target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def})
150 156  
  157 +message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}")
  158 +
151 159 if(TARGET Boost::boost)
  160 + message(STATUS "including boost target")
152 161 target_link_libraries(informational PRIVATE Boost::boost)
153 162 target_link_libraries(OptionalTest PRIVATE Boost::boost)
  163 + target_link_libraries(BoostOptionTypeTest PRIVATE Boost::boost)
  164 + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
  165 + target_link_libraries(OptionalTest_Single PRIVATE Boost::boost)
  166 + target_link_libraries(BoostOptionTypeTest_Single PRIVATE Boost::boost)
  167 + endif()
154 168 elseif(BOOST_FOUND)
  169 +message(STATUS "no boost target")
155 170 target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS})
156 171 target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS})
  172 + target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS})
  173 + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
  174 + target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
  175 + target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
  176 + endif()
157 177 endif()
158 178  
159 179 if(CMAKE_BUILD_TYPE STREQUAL Coverage)
... ...
tests/ComplexTypeTest.cpp 0 β†’ 100644
  1 +#include "app_helper.hpp"
  2 +#include "gmock/gmock.h"
  3 +#include <complex>
  4 +#include <cstdint>
  5 +
  6 +using ::testing::HasSubstr;
  7 +
  8 +using cx = std::complex<double>;
  9 +
  10 +CLI::Option *
  11 +add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
  12 + CLI::callback_t fun = [&variable](CLI::results_t res) {
  13 + double x, y;
  14 + bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
  15 + if(worked)
  16 + variable = cx(x, y);
  17 + return worked;
  18 + };
  19 +
  20 + CLI::Option *opt = app.add_option(name, fun, description, defaulted);
  21 + opt->type_name("COMPLEX")->type_size(2);
  22 + if(defaulted) {
  23 + std::stringstream out;
  24 + out << variable;
  25 + opt->default_str(out.str());
  26 + }
  27 + return opt;
  28 +}
  29 +
  30 +TEST_F(TApp, AddingComplexParser) {
  31 +
  32 + cx comp{0, 0};
  33 + add_option(app, "-c,--complex", comp);
  34 + args = {"-c", "1.5", "2.5"};
  35 +
  36 + run();
  37 +
  38 + EXPECT_DOUBLE_EQ(1.5, comp.real());
  39 + EXPECT_DOUBLE_EQ(2.5, comp.imag());
  40 +}
  41 +
  42 +TEST_F(TApp, DefaultedComplex) {
  43 +
  44 + cx comp{1, 2};
  45 + add_option(app, "-c,--complex", comp, "", true);
  46 + args = {"-c", "4", "3"};
  47 +
  48 + std::string help = app.help();
  49 + EXPECT_THAT(help, HasSubstr("1"));
  50 + EXPECT_THAT(help, HasSubstr("2"));
  51 +
  52 + EXPECT_DOUBLE_EQ(1, comp.real());
  53 + EXPECT_DOUBLE_EQ(2, comp.imag());
  54 +
  55 + run();
  56 +
  57 + EXPECT_DOUBLE_EQ(4, comp.real());
  58 + EXPECT_DOUBLE_EQ(3, comp.imag());
  59 +}
  60 +
  61 +// an example of custom complex number converter that can be used to add new parsing options
  62 +#if defined(__has_include)
  63 +#if __has_include(<regex>)
  64 +// an example of custom converter that can be used to add new parsing options
  65 +#define HAS_REGEX_INCLUDE
  66 +#endif
  67 +#endif
  68 +
  69 +#ifdef HAS_REGEX_INCLUDE
  70 +// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
  71 +// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
  72 +// simplify compilation and prevent a much more complicated #if expression
  73 +#include <regex>
  74 +namespace CLI {
  75 +namespace detail {
  76 +
  77 +// On MSVC and possibly some other new compilers this can be a free standing function without the template
  78 +// specialization but this is compiler dependent
  79 +template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
  80 + // regular expression to handle complex numbers of various formats
  81 + static const std::regex creg(
  82 + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
  83 +
  84 + std::smatch m;
  85 + double x{0.0}, y{0.0};
  86 + bool worked;
  87 + std::regex_search(input, m, creg);
  88 + if(m.size() == 9) {
  89 + worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
  90 + if(worked) {
  91 + if(*m[5].first == '-') {
  92 + y = -y;
  93 + }
  94 + }
  95 + } else {
  96 + if((input.back() == 'j') || (input.back() == 'i')) {
  97 + auto strval = input.substr(0, input.size() - 1);
  98 + CLI::detail::trim(strval);
  99 + worked = CLI::detail::lexical_cast(strval, y);
  100 + } else {
  101 + std::string ival = input;
  102 + CLI::detail::trim(ival);
  103 + worked = CLI::detail::lexical_cast(ival, x);
  104 + }
  105 + }
  106 + if(worked) {
  107 + output = cx{x, y};
  108 + }
  109 + return worked;
  110 +}
  111 +} // namespace detail
  112 +} // namespace CLI
  113 +
  114 +TEST_F(TApp, AddingComplexParserDetail) {
  115 +
  116 + bool skip_tests = false;
  117 + try { // check if the library actually supports regex, it is possible to link against a non working regex in the
  118 + // standard library
  119 + std::smatch m;
  120 + std::string input = "1.5+2.5j";
  121 + static const std::regex creg(
  122 + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
  123 +
  124 + auto rsearch = std::regex_search(input, m, creg);
  125 + if(!rsearch) {
  126 + skip_tests = true;
  127 + } else {
  128 + EXPECT_EQ(m.size(), 9u);
  129 + }
  130 +
  131 + } catch(...) {
  132 + skip_tests = true;
  133 + }
  134 + static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation");
  135 + if(!skip_tests) {
  136 + cx comp{0, 0};
  137 +
  138 + app.add_option("-c,--complex", comp, "add a complex number option");
  139 + args = {"-c", "1.5+2.5j"};
  140 +
  141 + run();
  142 +
  143 + EXPECT_DOUBLE_EQ(1.5, comp.real());
  144 + EXPECT_DOUBLE_EQ(2.5, comp.imag());
  145 + args = {"-c", "1.5-2.5j"};
  146 +
  147 + run();
  148 +
  149 + EXPECT_DOUBLE_EQ(1.5, comp.real());
  150 + EXPECT_DOUBLE_EQ(-2.5, comp.imag());
  151 + }
  152 +}
  153 +#endif
  154 +// defining a new complex class
  155 +class complex_new {
  156 + public:
  157 + complex_new() = default;
  158 + complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {};
  159 + double real() { return val1_; }
  160 + double imag() { return val2_; }
  161 +
  162 + private:
  163 + double val1_{0.0};
  164 + double val2_{0.0};
  165 +};
  166 +
  167 +TEST_F(TApp, newComplex) {
  168 + complex_new cval;
  169 + static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type");
  170 + static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number,
  171 + "complex new does not result in complex number categorization");
  172 + app.add_option("-c,--complex", cval, "add a complex number option");
  173 + args = {"-c", "1.5+2.5j"};
  174 +
  175 + run();
  176 +
  177 + EXPECT_DOUBLE_EQ(1.5, cval.real());
  178 + EXPECT_DOUBLE_EQ(2.5, cval.imag());
  179 + args = {"-c", "1.5-2.5j"};
  180 +
  181 + run();
  182 +
  183 + EXPECT_DOUBLE_EQ(1.5, cval.real());
  184 + EXPECT_DOUBLE_EQ(-2.5, cval.imag());
  185 +}
... ...
tests/HelpersTest.cpp
... ... @@ -6,8 +6,10 @@
6 6 #include <cstdint>
7 7 #include <cstdio>
8 8 #include <fstream>
  9 +#include <map>
9 10 #include <string>
10 11 #include <tuple>
  12 +#include <unordered_map>
11 13 #include <utility>
12 14  
13 15 class NotStreamable {};
... ... @@ -52,6 +54,57 @@ TEST(TypeTools, type_size) {
52 54 EXPECT_EQ(V, 5);
53 55 V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value;
54 56 EXPECT_EQ(V, 2);
  57 + V = CLI::detail::type_count<std::tuple<std::pair<std::string, double>>>::value;
  58 + EXPECT_EQ(V, 2);
  59 + V = CLI::detail::type_count<std::tuple<int, std::pair<std::string, double>>>::value;
  60 + EXPECT_EQ(V, 3);
  61 + V = CLI::detail::type_count<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value;
  62 + EXPECT_EQ(V, 4);
  63 + // maps
  64 + V = CLI::detail::type_count<std::map<int, std::pair<int, double>>>::value;
  65 + EXPECT_EQ(V, 3);
  66 + // three level tuples
  67 + V = CLI::detail::type_count<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value;
  68 + EXPECT_EQ(V, 5);
  69 + V = CLI::detail::type_count<std::pair<int, std::vector<int>>>::value;
  70 + EXPECT_GE(V, CLI::detail::expected_max_vector_size);
  71 + V = CLI::detail::type_count<std::vector<std::vector<int>>>::value;
  72 + EXPECT_EQ(V, CLI::detail::expected_max_vector_size);
  73 +}
  74 +
  75 +TEST(TypeTools, type_size_min) {
  76 + auto V = CLI::detail::type_count_min<int>::value;
  77 + EXPECT_EQ(V, 1);
  78 + V = CLI::detail::type_count_min<void>::value;
  79 + EXPECT_EQ(V, 0);
  80 + V = CLI::detail::type_count_min<std::vector<double>>::value;
  81 + EXPECT_EQ(V, 1);
  82 + V = CLI::detail::type_count_min<std::tuple<double, int>>::value;
  83 + EXPECT_EQ(V, 2);
  84 + V = CLI::detail::type_count_min<std::tuple<std::string, double, int>>::value;
  85 + EXPECT_EQ(V, 3);
  86 + V = CLI::detail::type_count_min<std::array<std::string, 5>>::value;
  87 + EXPECT_EQ(V, 5);
  88 + V = CLI::detail::type_count_min<std::vector<std::pair<std::string, double>>>::value;
  89 + EXPECT_EQ(V, 2);
  90 + V = CLI::detail::type_count_min<std::tuple<std::pair<std::string, double>>>::value;
  91 + EXPECT_EQ(V, 2);
  92 + V = CLI::detail::type_count_min<std::tuple<int, std::pair<std::string, double>>>::value;
  93 + EXPECT_EQ(V, 3);
  94 + V = CLI::detail::type_count_min<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value;
  95 + EXPECT_EQ(V, 4);
  96 + // maps
  97 + V = CLI::detail::type_count_min<std::map<int, std::pair<int, double>>>::value;
  98 + EXPECT_EQ(V, 3);
  99 + // three level tuples
  100 + V = CLI::detail::type_count_min<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value;
  101 + EXPECT_EQ(V, 5);
  102 + V = CLI::detail::type_count_min<std::pair<int, std::vector<int>>>::value;
  103 + EXPECT_EQ(V, 2);
  104 + V = CLI::detail::type_count_min<std::vector<std::vector<int>>>::value;
  105 + EXPECT_EQ(V, 1);
  106 + V = CLI::detail::type_count_min<std::vector<std::vector<std::pair<int, int>>>>::value;
  107 + EXPECT_EQ(V, 2);
55 108 }
56 109  
57 110 TEST(TypeTools, expected_count) {
... ... @@ -862,6 +915,10 @@ TEST(Types, TypeName) {
862 915 CLI::detail::object_category::tuple_value,
863 916 "pair<int,string> does not read like a tuple");
864 917  
  918 + static_assert(CLI::detail::classify_object<std::tuple<std::string, double>>::value ==
  919 + CLI::detail::object_category::tuple_value,
  920 + "tuple<string,double> does not read like a tuple");
  921 +
865 922 std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>();
866 923 EXPECT_EQ("[INT,TEXT]", pair_name);
867 924  
... ... @@ -869,7 +926,7 @@ TEST(Types, TypeName) {
869 926 EXPECT_EQ("UINT", vector_name);
870 927  
871 928 auto vclass = CLI::detail::classify_object<std::vector<std::vector<unsigned char>>>::value;
872   - EXPECT_EQ(vclass, CLI::detail::object_category::vector_value);
  929 + EXPECT_EQ(vclass, CLI::detail::object_category::container_value);
873 930  
874 931 auto tclass = CLI::detail::classify_object<std::tuple<double>>::value;
875 932 EXPECT_EQ(tclass, CLI::detail::object_category::number_constructible);
... ... @@ -883,6 +940,18 @@ TEST(Types, TypeName) {
883 940 tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>();
884 941 EXPECT_EQ("[INT,TEXT]", tuple_name);
885 942  
  943 + tuple_name = CLI::detail::type_name<std::tuple<const int, std::string>>();
  944 + EXPECT_EQ("[INT,TEXT]", tuple_name);
  945 +
  946 + tuple_name = CLI::detail::type_name<const std::tuple<int, std::string>>();
  947 + EXPECT_EQ("[INT,TEXT]", tuple_name);
  948 +
  949 + tuple_name = CLI::detail::type_name<std::tuple<std::string, double>>();
  950 + EXPECT_EQ("[TEXT,FLOAT]", tuple_name);
  951 +
  952 + tuple_name = CLI::detail::type_name<const std::tuple<std::string, double>>();
  953 + EXPECT_EQ("[TEXT,FLOAT]", tuple_name);
  954 +
886 955 tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>();
887 956 EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name);
888 957  
... ... @@ -911,6 +980,8 @@ TEST(Types, TypeName) {
911 980 "tuple<test> does not classify as a tuple");
912 981 std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>();
913 982 EXPECT_EQ("ENUM", enum_name2);
  983 + std::string umapName = CLI::detail::type_name<std::unordered_map<int, std::tuple<std::string, double>>>();
  984 + EXPECT_EQ("[INT,[TEXT,FLOAT]]", umapName);
914 985 }
915 986  
916 987 TEST(Types, OverflowSmall) {
... ... @@ -995,11 +1066,11 @@ TEST(Types, LexicalCastParsable) {
995 1066 std::complex<double> output;
996 1067 EXPECT_TRUE(CLI::detail::lexical_cast(input, output));
997 1068 EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble
998   - EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const
  1069 + EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + gcc 4.8 due to missing const
999 1070  
1000 1071 EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output));
1001 1072 EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble
1002   - EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + c++4.8 due to missing const
  1073 + EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + gcc 4.8 due to missing const
1003 1074  
1004 1075 EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
1005 1076 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
... ... @@ -1173,6 +1244,26 @@ TEST(Types, LexicalConversionComplex) {
1173 1244 EXPECT_EQ(x.imag(), 3.5);
1174 1245 }
1175 1246  
  1247 +static_assert(CLI::detail::is_wrapper<std::vector<double>>::value, "vector double should be a wrapper");
  1248 +static_assert(CLI::detail::is_wrapper<std::vector<std::string>>::value, "vector string should be a wrapper");
  1249 +static_assert(CLI::detail::is_wrapper<std::string>::value, "string should be a wrapper");
  1250 +static_assert(!CLI::detail::is_wrapper<double>::value, "double should not be a wrapper");
  1251 +
  1252 +static_assert(CLI::detail::is_mutable_container<std::vector<double>>::value, "vector class should be a container");
  1253 +static_assert(CLI::detail::is_mutable_container<std::vector<std::string>>::value, "vector class should be a container");
  1254 +static_assert(!CLI::detail::is_mutable_container<std::string>::value, "string should be a container");
  1255 +static_assert(!CLI::detail::is_mutable_container<double>::value, "double should not be a container");
  1256 +static_assert(!CLI::detail::is_mutable_container<std::array<double, 5>>::value, "array should not be a container");
  1257 +
  1258 +static_assert(CLI::detail::is_mutable_container<std::vector<int>>::value, "vector int should be a container");
  1259 +
  1260 +static_assert(CLI::detail::is_readable_container<std::vector<int> &>::value,
  1261 + "vector int & should be a readable container");
  1262 +static_assert(CLI::detail::is_readable_container<const std::vector<int>>::value,
  1263 + "const vector int should be a readable container");
  1264 +static_assert(CLI::detail::is_readable_container<const std::vector<int> &>::value,
  1265 + "const vector int & should be a readable container");
  1266 +
1176 1267 TEST(FixNewLines, BasicCheck) {
1177 1268 std::string input = "one\ntwo";
1178 1269 std::string output = "one\n; two";
... ...
tests/NewParseTest.cpp
... ... @@ -7,47 +7,36 @@ using ::testing::HasSubstr;
7 7  
8 8 using cx = std::complex<double>;
9 9  
10   -CLI::Option *
11   -add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
12   - CLI::callback_t fun = [&variable](CLI::results_t res) {
13   - double x, y;
14   - bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
15   - if(worked)
16   - variable = cx(x, y);
17   - return worked;
18   - };
19   -
20   - CLI::Option *opt = app.add_option(name, fun, description, defaulted);
21   - opt->type_name("COMPLEX")->type_size(2);
22   - if(defaulted) {
23   - std::stringstream out;
24   - out << variable;
25   - opt->default_str(out.str());
26   - }
27   - return opt;
28   -}
  10 +TEST_F(TApp, Complex) {
  11 + cx comp{1, 2};
  12 + app.add_complex("-c,--complex", comp, "", true);
  13 +
  14 + args = {"-c", "4", "3"};
29 15  
30   -TEST_F(TApp, AddingComplexParser) {
  16 + std::string help = app.help();
  17 + EXPECT_THAT(help, HasSubstr("1"));
  18 + EXPECT_THAT(help, HasSubstr("2"));
  19 + EXPECT_THAT(help, HasSubstr("COMPLEX"));
31 20  
32   - cx comp{0, 0};
33   - add_option(app, "-c,--complex", comp);
34   - args = {"-c", "1.5", "2.5"};
  21 + EXPECT_DOUBLE_EQ(1, comp.real());
  22 + EXPECT_DOUBLE_EQ(2, comp.imag());
35 23  
36 24 run();
37 25  
38   - EXPECT_DOUBLE_EQ(1.5, comp.real());
39   - EXPECT_DOUBLE_EQ(2.5, comp.imag());
  26 + EXPECT_DOUBLE_EQ(4, comp.real());
  27 + EXPECT_DOUBLE_EQ(3, comp.imag());
40 28 }
41 29  
42   -TEST_F(TApp, DefaultComplex) {
43   -
  30 +TEST_F(TApp, ComplexOption) {
44 31 cx comp{1, 2};
45   - add_option(app, "-c,--complex", comp, "", true);
  32 + app.add_option("-c,--complex", comp, "", true);
  33 +
46 34 args = {"-c", "4", "3"};
47 35  
48 36 std::string help = app.help();
49 37 EXPECT_THAT(help, HasSubstr("1"));
50 38 EXPECT_THAT(help, HasSubstr("2"));
  39 + EXPECT_THAT(help, HasSubstr("COMPLEX"));
51 40  
52 41 EXPECT_DOUBLE_EQ(1, comp.real());
53 42 EXPECT_DOUBLE_EQ(2, comp.imag());
... ... @@ -58,9 +47,9 @@ TEST_F(TApp, DefaultComplex) {
58 47 EXPECT_DOUBLE_EQ(3, comp.imag());
59 48 }
60 49  
61   -TEST_F(TApp, BuiltinComplex) {
62   - cx comp{1, 2};
63   - app.add_complex("-c,--complex", comp, "", true);
  50 +TEST_F(TApp, ComplexFloat) {
  51 + std::complex<float> comp{1, 2};
  52 + app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true);
64 53  
65 54 args = {"-c", "4", "3"};
66 55  
... ... @@ -69,18 +58,18 @@ TEST_F(TApp, BuiltinComplex) {
69 58 EXPECT_THAT(help, HasSubstr("2"));
70 59 EXPECT_THAT(help, HasSubstr("COMPLEX"));
71 60  
72   - EXPECT_DOUBLE_EQ(1, comp.real());
73   - EXPECT_DOUBLE_EQ(2, comp.imag());
  61 + EXPECT_FLOAT_EQ(1, comp.real());
  62 + EXPECT_FLOAT_EQ(2, comp.imag());
74 63  
75 64 run();
76 65  
77   - EXPECT_DOUBLE_EQ(4, comp.real());
78   - EXPECT_DOUBLE_EQ(3, comp.imag());
  66 + EXPECT_FLOAT_EQ(4, comp.real());
  67 + EXPECT_FLOAT_EQ(3, comp.imag());
79 68 }
80 69  
81   -TEST_F(TApp, BuiltinComplexFloat) {
  70 +TEST_F(TApp, ComplexFloatOption) {
82 71 std::complex<float> comp{1, 2};
83   - app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true);
  72 + app.add_option("-c,--complex", comp, "", true);
84 73  
85 74 args = {"-c", "4", "3"};
86 75  
... ... @@ -98,7 +87,7 @@ TEST_F(TApp, BuiltinComplexFloat) {
98 87 EXPECT_FLOAT_EQ(3, comp.imag());
99 88 }
100 89  
101   -TEST_F(TApp, BuiltinComplexWithDelimiter) {
  90 +TEST_F(TApp, ComplexWithDelimiter) {
102 91 cx comp{1, 2};
103 92 app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
104 93  
... ... @@ -130,7 +119,39 @@ TEST_F(TApp, BuiltinComplexWithDelimiter) {
130 119 EXPECT_DOUBLE_EQ(-4, comp.imag());
131 120 }
132 121  
133   -TEST_F(TApp, BuiltinComplexIgnoreI) {
  122 +TEST_F(TApp, ComplexWithDelimiterOption) {
  123 + cx comp{1, 2};
  124 + app.add_option("-c,--complex", comp, "", true)->delimiter('+');
  125 +
  126 + args = {"-c", "4+3i"};
  127 +
  128 + std::string help = app.help();
  129 + EXPECT_THAT(help, HasSubstr("1"));
  130 + EXPECT_THAT(help, HasSubstr("2"));
  131 + EXPECT_THAT(help, HasSubstr("COMPLEX"));
  132 +
  133 + EXPECT_DOUBLE_EQ(1, comp.real());
  134 + EXPECT_DOUBLE_EQ(2, comp.imag());
  135 +
  136 + run();
  137 +
  138 + EXPECT_DOUBLE_EQ(4, comp.real());
  139 + EXPECT_DOUBLE_EQ(3, comp.imag());
  140 +
  141 + args = {"-c", "5+-3i"};
  142 + run();
  143 +
  144 + EXPECT_DOUBLE_EQ(5, comp.real());
  145 + EXPECT_DOUBLE_EQ(-3, comp.imag());
  146 +
  147 + args = {"-c", "6", "-4i"};
  148 + run();
  149 +
  150 + EXPECT_DOUBLE_EQ(6, comp.real());
  151 + EXPECT_DOUBLE_EQ(-4, comp.imag());
  152 +}
  153 +
  154 +TEST_F(TApp, ComplexIgnoreI) {
134 155 cx comp{1, 2};
135 156 app.add_complex("-c,--complex", comp);
136 157  
... ... @@ -142,7 +163,19 @@ TEST_F(TApp, BuiltinComplexIgnoreI) {
142 163 EXPECT_DOUBLE_EQ(3, comp.imag());
143 164 }
144 165  
145   -TEST_F(TApp, BuiltinComplexSingleArg) {
  166 +TEST_F(TApp, ComplexIgnoreIOption) {
  167 + cx comp{1, 2};
  168 + app.add_option("-c,--complex", comp);
  169 +
  170 + args = {"-c", "4", "3i"};
  171 +
  172 + run();
  173 +
  174 + EXPECT_DOUBLE_EQ(4, comp.real());
  175 + EXPECT_DOUBLE_EQ(3, comp.imag());
  176 +}
  177 +
  178 +TEST_F(TApp, ComplexSingleArg) {
146 179 cx comp{1, 2};
147 180 app.add_complex("-c,--complex", comp);
148 181  
... ... @@ -176,7 +209,41 @@ TEST_F(TApp, BuiltinComplexSingleArg) {
176 209 EXPECT_DOUBLE_EQ(-2.7, comp.imag());
177 210 }
178 211  
179   -TEST_F(TApp, BuiltinComplexSingleImag) {
  212 +TEST_F(TApp, ComplexSingleArgOption) {
  213 + cx comp{1, 2};
  214 + app.add_option("-c,--complex", comp);
  215 +
  216 + args = {"-c", "4"};
  217 + run();
  218 + EXPECT_DOUBLE_EQ(4, comp.real());
  219 + EXPECT_DOUBLE_EQ(0, comp.imag());
  220 +
  221 + args = {"-c", "4-2i"};
  222 + run();
  223 + EXPECT_DOUBLE_EQ(4, comp.real());
  224 + EXPECT_DOUBLE_EQ(-2, comp.imag());
  225 + args = {"-c", "4+2i"};
  226 + run();
  227 + EXPECT_DOUBLE_EQ(4, comp.real());
  228 + EXPECT_DOUBLE_EQ(2, comp.imag());
  229 +
  230 + args = {"-c", "-4+2j"};
  231 + run();
  232 + EXPECT_DOUBLE_EQ(-4, comp.real());
  233 + EXPECT_DOUBLE_EQ(2, comp.imag());
  234 +
  235 + args = {"-c", "-4.2-2j"};
  236 + run();
  237 + EXPECT_DOUBLE_EQ(-4.2, comp.real());
  238 + EXPECT_DOUBLE_EQ(-2, comp.imag());
  239 +
  240 + args = {"-c", "-4.2-2.7i"};
  241 + run();
  242 + EXPECT_DOUBLE_EQ(-4.2, comp.real());
  243 + EXPECT_DOUBLE_EQ(-2.7, comp.imag());
  244 +}
  245 +
  246 +TEST_F(TApp, ComplexSingleImag) {
180 247 cx comp{1, 2};
181 248 app.add_complex("-c,--complex", comp);
182 249  
... ... @@ -199,6 +266,29 @@ TEST_F(TApp, BuiltinComplexSingleImag) {
199 266 EXPECT_DOUBLE_EQ(0, comp.imag());
200 267 }
201 268  
  269 +TEST_F(TApp, ComplexSingleImagOption) {
  270 + cx comp{1, 2};
  271 + app.add_option("-c,--complex", comp);
  272 +
  273 + args = {"-c", "4j"};
  274 + run();
  275 + EXPECT_DOUBLE_EQ(0, comp.real());
  276 + EXPECT_DOUBLE_EQ(4, comp.imag());
  277 +
  278 + args = {"-c", "-4j"};
  279 + run();
  280 + EXPECT_DOUBLE_EQ(0, comp.real());
  281 + EXPECT_DOUBLE_EQ(-4, comp.imag());
  282 + args = {"-c", "-4"};
  283 + run();
  284 + EXPECT_DOUBLE_EQ(-4, comp.real());
  285 + EXPECT_DOUBLE_EQ(0, comp.imag());
  286 + args = {"-c", "+4"};
  287 + run();
  288 + EXPECT_DOUBLE_EQ(4, comp.real());
  289 + EXPECT_DOUBLE_EQ(0, comp.imag());
  290 +}
  291 +
202 292 /// Simple class containing two strings useful for testing lexical cast and conversions
203 293 class spair {
204 294 public:
... ... @@ -245,98 +335,6 @@ TEST_F(TApp, custom_string_converterFail) {
245 335 EXPECT_THROW(run(), CLI::ConversionError);
246 336 }
247 337  
248   -// an example of custom complex number converter that can be used to add new parsing options
249   -#if defined(__has_include)
250   -#if __has_include(<regex>)
251   -// an example of custom converter that can be used to add new parsing options
252   -#define HAS_REGEX_INCLUDE
253   -#endif
254   -#endif
255   -
256   -#ifdef HAS_REGEX_INCLUDE
257   -// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
258   -// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
259   -// simplify compilation and prevent a much more complicated #if expression
260   -#include <regex>
261   -namespace CLI {
262   -namespace detail {
263   -
264   -// On MSVC and possibly some other new compilers this can be a free standing function without the template
265   -// specialization but this is compiler dependent
266   -template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
267   - // regular expression to handle complex numbers of various formats
268   - static const std::regex creg(
269   - R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
270   -
271   - std::smatch m;
272   - double x{0.0}, y{0.0};
273   - bool worked;
274   - std::regex_search(input, m, creg);
275   - if(m.size() == 9) {
276   - worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
277   - if(worked) {
278   - if(*m[5].first == '-') {
279   - y = -y;
280   - }
281   - }
282   - } else {
283   - if((input.back() == 'j') || (input.back() == 'i')) {
284   - auto strval = input.substr(0, input.size() - 1);
285   - CLI::detail::trim(strval);
286   - worked = CLI::detail::lexical_cast(strval, y);
287   - } else {
288   - std::string ival = input;
289   - CLI::detail::trim(ival);
290   - worked = CLI::detail::lexical_cast(ival, x);
291   - }
292   - }
293   - if(worked) {
294   - output = cx{x, y};
295   - }
296   - return worked;
297   -}
298   -} // namespace detail
299   -} // namespace CLI
300   -
301   -TEST_F(TApp, AddingComplexParserDetail) {
302   -
303   - bool skip_tests = false;
304   - try { // check if the library actually supports regex, it is possible to link against a non working regex in the
305   - // standard library
306   - std::smatch m;
307   - std::string input = "1.5+2.5j";
308   - static const std::regex creg(
309   - R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
310   -
311   - auto rsearch = std::regex_search(input, m, creg);
312   - if(!rsearch) {
313   - skip_tests = true;
314   - } else {
315   - EXPECT_EQ(m.size(), 9u);
316   - }
317   -
318   - } catch(...) {
319   - skip_tests = true;
320   - }
321   - if(!skip_tests) {
322   - cx comp{0, 0};
323   - app.add_option("-c,--complex", comp, "add a complex number option");
324   - args = {"-c", "1.5+2.5j"};
325   -
326   - run();
327   -
328   - EXPECT_DOUBLE_EQ(1.5, comp.real());
329   - EXPECT_DOUBLE_EQ(2.5, comp.imag());
330   - args = {"-c", "1.5-2.5j"};
331   -
332   - run();
333   -
334   - EXPECT_DOUBLE_EQ(1.5, comp.real());
335   - EXPECT_DOUBLE_EQ(-2.5, comp.imag());
336   - }
337   -}
338   -#endif
339   -
340 338 /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
341 339 /// option assignments
342 340 template <class X> class objWrapper {
... ... @@ -523,3 +521,107 @@ TEST_F(TApp, uint16Wrapper) {
523 521  
524 522 EXPECT_THROW(run(), CLI::ConversionError);
525 523 }
  524 +
  525 +template <class T> class SimpleWrapper {
  526 + public:
  527 + SimpleWrapper() : val_{} {};
  528 + explicit SimpleWrapper(const T &initial) : val_{initial} {};
  529 + T &getRef() { return val_; };
  530 + using value_type = T;
  531 +
  532 + private:
  533 + T val_;
  534 +};
  535 +
  536 +TEST_F(TApp, wrapperInt) {
  537 + SimpleWrapper<int> wrap;
  538 + app.add_option("--val", wrap);
  539 + args = {"--val", "2"};
  540 +
  541 + run();
  542 + EXPECT_EQ(wrap.getRef(), 2);
  543 +}
  544 +
  545 +TEST_F(TApp, wrapperString) {
  546 + SimpleWrapper<std::string> wrap;
  547 + app.add_option("--val", wrap);
  548 + args = {"--val", "str"};
  549 +
  550 + run();
  551 + EXPECT_EQ(wrap.getRef(), "str");
  552 +}
  553 +
  554 +TEST_F(TApp, wrapperVector) {
  555 + SimpleWrapper<std::vector<int>> wrap;
  556 + app.add_option("--val", wrap);
  557 + args = {"--val", "1", "2", "3", "4"};
  558 +
  559 + run();
  560 + auto v1 = wrap.getRef();
  561 + auto v2 = std::vector<int>{1, 2, 3, 4};
  562 + EXPECT_EQ(v1, v2);
  563 +}
  564 +
  565 +TEST_F(TApp, wrapperwrapperString) {
  566 + SimpleWrapper<SimpleWrapper<std::string>> wrap;
  567 + app.add_option("--val", wrap);
  568 + args = {"--val", "arg"};
  569 +
  570 + run();
  571 + auto v1 = wrap.getRef().getRef();
  572 + auto v2 = "arg";
  573 + EXPECT_EQ(v1, v2);
  574 +}
  575 +
  576 +TEST_F(TApp, wrapperwrapperVector) {
  577 + SimpleWrapper<SimpleWrapper<std::vector<int>>> wrap;
  578 + auto opt = app.add_option("--val", wrap);
  579 + args = {"--val", "1", "2", "3", "4"};
  580 +
  581 + run();
  582 + auto v1 = wrap.getRef().getRef();
  583 + auto v2 = std::vector<int>{1, 2, 3, 4};
  584 + EXPECT_EQ(v1, v2);
  585 + opt->type_size(0, 5);
  586 +
  587 + args = {"--val"};
  588 +
  589 + run();
  590 + EXPECT_TRUE(wrap.getRef().getRef().empty());
  591 +
  592 + args = {"--val", "happy", "sad"};
  593 +
  594 + EXPECT_THROW(run(), CLI::ConversionError);
  595 +}
  596 +
  597 +TEST_F(TApp, wrapperComplex) {
  598 + SimpleWrapper<std::complex<double>> wrap;
  599 + app.add_option("--val", wrap);
  600 + args = {"--val", "1", "2"};
  601 +
  602 + run();
  603 + auto &v1 = wrap.getRef();
  604 + auto v2 = std::complex<double>{1, 2};
  605 + EXPECT_EQ(v1.real(), v2.real());
  606 + EXPECT_EQ(v1.imag(), v2.imag());
  607 + args = {"--val", "1.4-4j"};
  608 +
  609 + run();
  610 + v2 = std::complex<double>{1.4, -4};
  611 + EXPECT_EQ(v1.real(), v2.real());
  612 + EXPECT_EQ(v1.imag(), v2.imag());
  613 +}
  614 +
  615 +TEST_F(TApp, vectorComplex) {
  616 + std::vector<std::complex<double>> vcomplex;
  617 + app.add_option("--val", vcomplex);
  618 + args = {"--val", "1", "2", "--val", "1.4-4j"};
  619 +
  620 + run();
  621 +
  622 + ASSERT_EQ(vcomplex.size(), 2U);
  623 + EXPECT_EQ(vcomplex[0].real(), 1.0);
  624 + EXPECT_EQ(vcomplex[0].imag(), 2.0);
  625 + EXPECT_EQ(vcomplex[1].real(), 1.4);
  626 + EXPECT_EQ(vcomplex[1].imag(), -4.0);
  627 +}
... ...
tests/OptionTypeTest.cpp 0 β†’ 100644
  1 +#include "app_helper.hpp"
  2 +#include <complex>
  3 +#include <cstdint>
  4 +#include <cstdlib>
  5 +#include <deque>
  6 +#include <forward_list>
  7 +#include <list>
  8 +#include <map>
  9 +#include <queue>
  10 +#include <set>
  11 +#include <unordered_map>
  12 +#include <unordered_set>
  13 +#include <vector>
  14 +
  15 +#include "gmock/gmock.h"
  16 +
  17 +TEST_F(TApp, OneStringAgain) {
  18 + std::string str;
  19 + app.add_option("-s,--string", str);
  20 + args = {"--string", "mystring"};
  21 + run();
  22 + EXPECT_EQ(1u, app.count("-s"));
  23 + EXPECT_EQ(1u, app.count("--string"));
  24 + EXPECT_EQ(str, "mystring");
  25 +}
  26 +
  27 +TEST_F(TApp, OneStringFunction) {
  28 + std::string str;
  29 + app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; });
  30 + args = {"--string", "mystring"};
  31 + run();
  32 + EXPECT_EQ(1u, app.count("-s"));
  33 + EXPECT_EQ(1u, app.count("--string"));
  34 + EXPECT_EQ(str, "mystring");
  35 +}
  36 +
  37 +TEST_F(TApp, doubleFunction) {
  38 + double res{0.0};
  39 + app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
  40 + args = {"--val", "-354.356"};
  41 + run();
  42 + EXPECT_EQ(res, 300.356);
  43 + // get the original value as entered as an integer
  44 + EXPECT_EQ(app["--val"]->as<float>(), -354.356f);
  45 +}
  46 +
  47 +TEST_F(TApp, doubleFunctionFail) {
  48 + double res;
  49 + app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
  50 + args = {"--val", "not_double"};
  51 + EXPECT_THROW(run(), CLI::ConversionError);
  52 +}
  53 +
  54 +TEST_F(TApp, doubleVectorFunction) {
  55 + std::vector<double> res;
  56 + app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
  57 + res = val;
  58 + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
  59 + });
  60 + args = {"--val", "5", "--val", "6", "--val", "7"};
  61 + run();
  62 + EXPECT_EQ(res.size(), 3u);
  63 + EXPECT_EQ(res[0], 10.0);
  64 + EXPECT_EQ(res[2], 12.0);
  65 +}
  66 +
  67 +TEST_F(TApp, doubleVectorFunctionFail) {
  68 + std::vector<double> res;
  69 + std::string vstring = "--val";
  70 + app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
  71 + res = val;
  72 + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
  73 + });
  74 + args = {"--val", "five", "--val", "nine", "--val", "7"};
  75 + EXPECT_THROW(run(), CLI::ConversionError);
  76 + // check that getting the results through the results function generates the same error
  77 + EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError);
  78 + auto strvec = app[vstring]->as<std::vector<std::string>>();
  79 + EXPECT_EQ(strvec.size(), 3u);
  80 +}
  81 +
  82 +TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) {
  83 + std::vector<double> res;
  84 + auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
  85 + res = val;
  86 + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
  87 + });
  88 + args = {"--val", "5", "--val", "6", "--val", "7"};
  89 + run();
  90 + EXPECT_EQ(res.size(), 3u);
  91 + EXPECT_EQ(res[0], 10.0);
  92 + EXPECT_EQ(res[2], 12.0);
  93 + EXPECT_FALSE(opt->get_run_callback_for_default());
  94 + opt->run_callback_for_default();
  95 + opt->default_val(std::vector<int>{2, 1, -2});
  96 + EXPECT_EQ(res[0], 7.0);
  97 + EXPECT_EQ(res[2], 3.0);
  98 +
  99 + EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError);
  100 + auto vec = opt->as<std::vector<double>>();
  101 + ASSERT_EQ(vec.size(), 3U);
  102 + EXPECT_EQ(vec[0], 5.0);
  103 + EXPECT_EQ(vec[2], 7.0);
  104 + opt->check(CLI::Number);
  105 + opt->run_callback_for_default(false);
  106 + EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError);
  107 +}
  108 +
  109 +TEST_F(TApp, BoolAndIntFlags) {
  110 +
  111 + bool bflag{false};
  112 + int iflag{0};
  113 + unsigned int uflag{0};
  114 +
  115 + app.add_flag("-b", bflag);
  116 + app.add_flag("-i", iflag);
  117 + app.add_flag("-u", uflag);
  118 +
  119 + args = {"-b", "-i", "-u"};
  120 + run();
  121 + EXPECT_TRUE(bflag);
  122 + EXPECT_EQ(1, iflag);
  123 + EXPECT_EQ((unsigned int)1, uflag);
  124 +
  125 + args = {"-b", "-b"};
  126 + ASSERT_NO_THROW(run());
  127 + EXPECT_TRUE(bflag);
  128 +
  129 + bflag = false;
  130 +
  131 + args = {"-iiiuu"};
  132 + run();
  133 + EXPECT_FALSE(bflag);
  134 + EXPECT_EQ(3, iflag);
  135 + EXPECT_EQ((unsigned int)2, uflag);
  136 +}
  137 +
  138 +TEST_F(TApp, BoolOption) {
  139 + bool bflag{false};
  140 + app.add_option("-b", bflag);
  141 +
  142 + args = {"-b", "false"};
  143 + run();
  144 + EXPECT_FALSE(bflag);
  145 +
  146 + args = {"-b", "1"};
  147 + run();
  148 + EXPECT_TRUE(bflag);
  149 +
  150 + args = {"-b", "-7"};
  151 + run();
  152 + EXPECT_FALSE(bflag);
  153 +
  154 + // cause an out of bounds error internally
  155 + args = {"-b", "751615654161688126132138844896646748852"};
  156 + run();
  157 + EXPECT_TRUE(bflag);
  158 +
  159 + args = {"-b", "-751615654161688126132138844896646748852"};
  160 + run();
  161 + EXPECT_FALSE(bflag);
  162 +}
  163 +
  164 +TEST_F(TApp, vectorDefaults) {
  165 + std::vector<int> vals{4, 5};
  166 + auto opt = app.add_option("--long", vals, "", true);
  167 +
  168 + args = {"--long", "[1,2,3]"};
  169 +
  170 + run();
  171 +
  172 + EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
  173 +
  174 + args.clear();
  175 + run();
  176 + auto res = app["--long"]->as<std::vector<int>>();
  177 + EXPECT_EQ(res, std::vector<int>({4, 5}));
  178 +
  179 + app.clear();
  180 + opt->expected(1)->take_last();
  181 + res = app["--long"]->as<std::vector<int>>();
  182 + EXPECT_EQ(res, std::vector<int>({5}));
  183 + opt->take_first();
  184 + res = app["--long"]->as<std::vector<int>>();
  185 + EXPECT_EQ(res, std::vector<int>({4}));
  186 +
  187 + opt->expected(0, 1)->take_last();
  188 + run();
  189 +
  190 + EXPECT_EQ(res, std::vector<int>({4}));
  191 + res = app["--long"]->as<std::vector<int>>();
  192 + EXPECT_EQ(res, std::vector<int>({5}));
  193 +}
  194 +
  195 +TEST_F(TApp, CallbackBoolFlags) {
  196 +
  197 + bool value{false};
  198 +
  199 + auto func = [&value]() { value = true; };
  200 +
  201 + auto cback = app.add_flag_callback("--val", func);
  202 + args = {"--val"};
  203 + run();
  204 + EXPECT_TRUE(value);
  205 + value = false;
  206 + args = {"--val=false"};
  207 + run();
  208 + EXPECT_FALSE(value);
  209 +
  210 + EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
  211 + cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
  212 + args = {"--val", "--val=false"};
  213 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  214 +}
  215 +
  216 +TEST_F(TApp, pair_check) {
  217 + std::string myfile{"pair_check_file.txt"};
  218 + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
  219 + EXPECT_TRUE(ok);
  220 +
  221 + EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
  222 + std::pair<std::string, int> findex;
  223 +
  224 + auto v0 = CLI::ExistingFile;
  225 + v0.application_index(0);
  226 + auto v1 = CLI::PositiveNumber;
  227 + v1.application_index(1);
  228 + app.add_option("--file", findex)->check(v0)->check(v1);
  229 +
  230 + args = {"--file", myfile, "2"};
  231 +
  232 + EXPECT_NO_THROW(run());
  233 +
  234 + EXPECT_EQ(findex.first, myfile);
  235 + EXPECT_EQ(findex.second, 2);
  236 +
  237 + args = {"--file", myfile, "-3"};
  238 +
  239 + EXPECT_THROW(run(), CLI::ValidationError);
  240 +
  241 + args = {"--file", myfile, "2"};
  242 + std::remove(myfile.c_str());
  243 + EXPECT_THROW(run(), CLI::ValidationError);
  244 +}
  245 +
  246 +// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
  247 +
  248 +TEST_F(TApp, pair_check_take_first) {
  249 + std::string myfile{"pair_check_file2.txt"};
  250 + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
  251 + EXPECT_TRUE(ok);
  252 +
  253 + EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
  254 + std::pair<std::string, int> findex;
  255 +
  256 + auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
  257 + EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound);
  258 + opt->get_validator(0)->application_index(0);
  259 + opt->get_validator(1)->application_index(1);
  260 + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
  261 + args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"};
  262 + // should only check the last one
  263 + EXPECT_NO_THROW(run());
  264 +
  265 + EXPECT_EQ(findex.first, myfile);
  266 + EXPECT_EQ(findex.second, 2);
  267 +
  268 + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
  269 +
  270 + EXPECT_THROW(run(), CLI::ValidationError);
  271 +}
  272 +
  273 +TEST_F(TApp, VectorFixedString) {
  274 + std::vector<std::string> strvec;
  275 + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
  276 +
  277 + CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3);
  278 + EXPECT_EQ(3, opt->get_expected());
  279 +
  280 + args = {"--string", "mystring", "mystring2", "mystring3"};
  281 + run();
  282 + EXPECT_EQ(3u, app.count("--string"));
  283 + EXPECT_EQ(answer, strvec);
  284 +}
  285 +
  286 +TEST_F(TApp, VectorDefaultedFixedString) {
  287 + std::vector<std::string> strvec{"one"};
  288 + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
  289 +
  290 + CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str();
  291 + EXPECT_EQ(3, opt->get_expected());
  292 +
  293 + args = {"--string", "mystring", "mystring2", "mystring3"};
  294 + run();
  295 + EXPECT_EQ(3u, app.count("--string"));
  296 + EXPECT_EQ(answer, strvec);
  297 +}
  298 +
  299 +TEST_F(TApp, VectorIndexedValidator) {
  300 + std::vector<int> vvec;
  301 +
  302 + CLI::Option *opt = app.add_option("-v", vvec);
  303 +
  304 + args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
  305 + run();
  306 + EXPECT_EQ(4u, app.count("-v"));
  307 + EXPECT_EQ(4u, vvec.size());
  308 + opt->check(CLI::PositiveNumber.application_index(0));
  309 + opt->check((!CLI::PositiveNumber).application_index(1));
  310 + EXPECT_NO_THROW(run());
  311 + EXPECT_EQ(4u, vvec.size());
  312 + // v[3] would be negative
  313 + opt->check(CLI::PositiveNumber.application_index(3));
  314 + EXPECT_THROW(run(), CLI::ValidationError);
  315 +}
  316 +
  317 +TEST_F(TApp, VectorUnlimString) {
  318 + std::vector<std::string> strvec;
  319 + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
  320 +
  321 + CLI::Option *opt = app.add_option("-s,--string", strvec);
  322 + EXPECT_EQ(1, opt->get_expected());
  323 + EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max());
  324 +
  325 + args = {"--string", "mystring", "mystring2", "mystring3"};
  326 + run();
  327 + EXPECT_EQ(3u, app.count("--string"));
  328 + EXPECT_EQ(answer, strvec);
  329 +
  330 + args = {"-s", "mystring", "mystring2", "mystring3"};
  331 + run();
  332 + EXPECT_EQ(3u, app.count("--string"));
  333 + EXPECT_EQ(answer, strvec);
  334 +}
  335 +
  336 +// From https://github.com/CLIUtils/CLI11/issues/420
  337 +TEST_F(TApp, stringLikeTests) {
  338 + struct nType {
  339 + explicit nType(const std::string &a_value) : m_value{a_value} {}
  340 +
  341 + explicit operator std::string() const { return std::string{"op str"}; }
  342 +
  343 + std::string m_value;
  344 + };
  345 +
  346 + nType m_type{"abc"};
  347 + app.add_option("--type", m_type, "type")->capture_default_str();
  348 + run();
  349 +
  350 + EXPECT_EQ(app["--type"]->as<std::string>(), "op str");
  351 + args = {"--type", "bca"};
  352 + run();
  353 + EXPECT_EQ(std::string(m_type), "op str");
  354 + EXPECT_EQ(m_type.m_value, "bca");
  355 +}
  356 +
  357 +TEST_F(TApp, VectorExpectedRange) {
  358 + std::vector<std::string> strvec;
  359 +
  360 + CLI::Option *opt = app.add_option("--string", strvec);
  361 + opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
  362 +
  363 + args = {"--string", "mystring", "mystring2", "mystring3"};
  364 + run();
  365 + EXPECT_EQ(3u, app.count("--string"));
  366 +
  367 + args = {"--string", "mystring"};
  368 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  369 +
  370 + args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"};
  371 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  372 +
  373 + EXPECT_EQ(opt->get_expected_max(), 4);
  374 + EXPECT_EQ(opt->get_expected_min(), 2);
  375 + opt->expected(4, 2); // just test the handling of reversed arguments
  376 + EXPECT_EQ(opt->get_expected_max(), 4);
  377 + EXPECT_EQ(opt->get_expected_min(), 2);
  378 + opt->expected(-5);
  379 + EXPECT_EQ(opt->get_expected_max(), 5);
  380 + EXPECT_EQ(opt->get_expected_min(), 5);
  381 + opt->expected(-5, 7);
  382 + EXPECT_EQ(opt->get_expected_max(), 7);
  383 + EXPECT_EQ(opt->get_expected_min(), 5);
  384 +}
  385 +
  386 +TEST_F(TApp, VectorFancyOpts) {
  387 + std::vector<std::string> strvec;
  388 + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
  389 +
  390 + CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3);
  391 + EXPECT_EQ(3, opt->get_expected());
  392 +
  393 + args = {"--string", "mystring", "mystring2", "mystring3"};
  394 + run();
  395 + EXPECT_EQ(3u, app.count("--string"));
  396 + EXPECT_EQ(answer, strvec);
  397 +
  398 + args = {"one", "two"};
  399 + EXPECT_THROW(run(), CLI::RequiredError);
  400 +
  401 + EXPECT_THROW(run(), CLI::ParseError);
  402 +}
  403 +
  404 +// #87
  405 +TEST_F(TApp, CustomDoubleOption) {
  406 +
  407 + std::pair<int, double> custom_opt;
  408 +
  409 + auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
  410 + custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
  411 + return true;
  412 + });
  413 + opt->type_name("INT FLOAT")->type_size(2);
  414 +
  415 + args = {"12", "1.5"};
  416 +
  417 + run();
  418 + EXPECT_EQ(custom_opt.first, 12);
  419 + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
  420 +}
  421 +
  422 +// now with tuple support this is possible
  423 +TEST_F(TApp, CustomDoubleOptionAlt) {
  424 +
  425 + std::pair<int, double> custom_opt;
  426 +
  427 + app.add_option("posit", custom_opt);
  428 +
  429 + args = {"12", "1.5"};
  430 +
  431 + run();
  432 + EXPECT_EQ(custom_opt.first, 12);
  433 + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
  434 +}
  435 +
  436 +// now with independent type sizes and expected this is possible
  437 +TEST_F(TApp, vectorPair) {
  438 +
  439 + std::vector<std::pair<int, std::string>> custom_opt;
  440 +
  441 + auto opt = app.add_option("--dict", custom_opt);
  442 +
  443 + args = {"--dict", "1", "str1", "--dict", "3", "str3"};
  444 +
  445 + run();
  446 + ASSERT_EQ(custom_opt.size(), 2u);
  447 + EXPECT_EQ(custom_opt[0].first, 1);
  448 + EXPECT_EQ(custom_opt[1].second, "str3");
  449 +
  450 + args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"};
  451 + run();
  452 + ASSERT_EQ(custom_opt.size(), 3u);
  453 + EXPECT_EQ(custom_opt[2].first, -1);
  454 + EXPECT_EQ(custom_opt[2].second, "str4");
  455 + opt->check(CLI::PositiveNumber.application_index(0));
  456 +
  457 + EXPECT_THROW(run(), CLI::ValidationError);
  458 +}
  459 +
  460 +TEST_F(TApp, vectorPairFail) {
  461 +
  462 + std::vector<std::pair<int, std::string>> custom_opt;
  463 +
  464 + app.add_option("--dict", custom_opt);
  465 +
  466 + args = {"--dict", "1", "str1", "--dict", "str3", "1"};
  467 +
  468 + EXPECT_THROW(run(), CLI::ConversionError);
  469 +}
  470 +
  471 +TEST_F(TApp, vectorPairTypeRange) {
  472 +
  473 + std::vector<std::pair<int, std::string>> custom_opt;
  474 +
  475 + auto opt = app.add_option("--dict", custom_opt);
  476 +
  477 + opt->type_size(2, 1); // just test switched arguments
  478 + EXPECT_EQ(opt->get_type_size_min(), 1);
  479 + EXPECT_EQ(opt->get_type_size_max(), 2);
  480 +
  481 + args = {"--dict", "1", "str1", "--dict", "3", "str3"};
  482 +
  483 + run();
  484 + ASSERT_EQ(custom_opt.size(), 2u);
  485 + EXPECT_EQ(custom_opt[0].first, 1);
  486 + EXPECT_EQ(custom_opt[1].second, "str3");
  487 +
  488 + args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
  489 + run();
  490 + ASSERT_EQ(custom_opt.size(), 3u);
  491 + EXPECT_TRUE(custom_opt[1].second.empty());
  492 + EXPECT_EQ(custom_opt[2].first, -1);
  493 + EXPECT_EQ(custom_opt[2].second, "str4");
  494 +
  495 + opt->type_size(-2, -1); // test negative arguments
  496 + EXPECT_EQ(opt->get_type_size_min(), 1);
  497 + EXPECT_EQ(opt->get_type_size_max(), 2);
  498 + // this type size spec should run exactly as before
  499 + run();
  500 + ASSERT_EQ(custom_opt.size(), 3u);
  501 + EXPECT_TRUE(custom_opt[1].second.empty());
  502 + EXPECT_EQ(custom_opt[2].first, -1);
  503 + EXPECT_EQ(custom_opt[2].second, "str4");
  504 +}
  505 +
  506 +// now with independent type sizes and expected this is possible
  507 +TEST_F(TApp, vectorTuple) {
  508 +
  509 + std::vector<std::tuple<int, std::string, double>> custom_opt;
  510 +
  511 + auto opt = app.add_option("--dict", custom_opt);
  512 +
  513 + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
  514 +
  515 + run();
  516 + ASSERT_EQ(custom_opt.size(), 2u);
  517 + EXPECT_EQ(std::get<0>(custom_opt[0]), 1);
  518 + EXPECT_EQ(std::get<1>(custom_opt[1]), "str3");
  519 + EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7);
  520 +
  521 + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
  522 + run();
  523 + ASSERT_EQ(custom_opt.size(), 3u);
  524 + EXPECT_EQ(std::get<0>(custom_opt[2]), -1);
  525 + EXPECT_EQ(std::get<1>(custom_opt[2]), "str4");
  526 + EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87);
  527 + opt->check(CLI::PositiveNumber.application_index(0));
  528 +
  529 + EXPECT_THROW(run(), CLI::ValidationError);
  530 +
  531 + args.back() = "haha";
  532 + args[9] = "45";
  533 + EXPECT_THROW(run(), CLI::ConversionError);
  534 +}
  535 +
  536 +// now with independent type sizes and expected this is possible
  537 +TEST_F(TApp, vectorVector) {
  538 +
  539 + std::vector<std::vector<int>> custom_opt;
  540 +
  541 + auto opt = app.add_option("--dict", custom_opt);
  542 +
  543 + args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
  544 +
  545 + run();
  546 + ASSERT_EQ(custom_opt.size(), 2u);
  547 + EXPECT_EQ(custom_opt[0].size(), 3u);
  548 + EXPECT_EQ(custom_opt[1].size(), 2u);
  549 +
  550 + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
  551 + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
  552 + run();
  553 + ASSERT_EQ(custom_opt.size(), 4u);
  554 + EXPECT_EQ(custom_opt[0].size(), 3u);
  555 + EXPECT_EQ(custom_opt[1].size(), 2u);
  556 + EXPECT_EQ(custom_opt[2].size(), 1u);
  557 + EXPECT_EQ(custom_opt[3].size(), 10u);
  558 + opt->check(CLI::PositiveNumber.application_index(9));
  559 +
  560 + EXPECT_THROW(run(), CLI::ValidationError);
  561 + args.pop_back();
  562 + EXPECT_NO_THROW(run());
  563 +
  564 + args.back() = "haha";
  565 + EXPECT_THROW(run(), CLI::ConversionError);
  566 +
  567 + args = {"--dict", "1", "2", "4", "%%", "3", "1", "%%", "3", "%%", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3"};
  568 + run();
  569 + ASSERT_EQ(custom_opt.size(), 4u);
  570 +}
  571 +
  572 +// now with independent type sizes and expected this is possible
  573 +TEST_F(TApp, vectorVectorFixedSize) {
  574 +
  575 + std::vector<std::vector<int>> custom_opt;
  576 +
  577 + auto opt = app.add_option("--dict", custom_opt)->type_size(4);
  578 +
  579 + args = {"--dict", "1", "2", "4", "3", "--dict", "3", "1", "2", "8"};
  580 +
  581 + run();
  582 + ASSERT_EQ(custom_opt.size(), 2u);
  583 + EXPECT_EQ(custom_opt[0].size(), 4u);
  584 + EXPECT_EQ(custom_opt[1].size(), 4u);
  585 +
  586 + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "7", "6"};
  587 + EXPECT_THROW(run(), CLI::ConversionError);
  588 + // this should reset it
  589 + opt->type_size(CLI::detail::expected_max_vector_size);
  590 + opt->type_size(1, CLI::detail::expected_max_vector_size);
  591 + EXPECT_NO_THROW(run());
  592 + ASSERT_EQ(custom_opt.size(), 2U);
  593 +}
  594 +
  595 +// now with independent type sizes and expected this is possible
  596 +TEST_F(TApp, tuplePair) {
  597 + std::tuple<std::pair<int, double>> custom_opt;
  598 +
  599 + app.add_option("--pr", custom_opt);
  600 +
  601 + args = {"--pr", "1", "2"};
  602 +
  603 + run();
  604 + EXPECT_EQ(std::get<0>(custom_opt).first, 1);
  605 + EXPECT_EQ(std::get<0>(custom_opt).second, 2.0);
  606 +}
  607 +// now with independent type sizes and expected this is possible
  608 +TEST_F(TApp, tupleintPair) {
  609 + std::tuple<int, std::pair<int, double>> custom_opt;
  610 +
  611 + app.add_option("--pr", custom_opt);
  612 +
  613 + args = {"--pr", "3", "1", "2"};
  614 +
  615 + run();
  616 + EXPECT_EQ(std::get<0>(custom_opt), 3);
  617 + EXPECT_EQ(std::get<1>(custom_opt).first, 1);
  618 + EXPECT_EQ(std::get<1>(custom_opt).second, 2.0);
  619 +}
  620 +
  621 +static_assert(CLI::detail::is_mutable_container<std::set<std::string>>::value, "set should be a container");
  622 +static_assert(CLI::detail::is_mutable_container<std::map<std::string, std::string>>::value,
  623 + "map should be a container");
  624 +static_assert(CLI::detail::is_mutable_container<std::unordered_map<std::string, double>>::value,
  625 + "unordered_map should be a container");
  626 +
  627 +static_assert(CLI::detail::is_mutable_container<std::list<std::pair<int, std::string>>>::value,
  628 + "list should be a container");
  629 +
  630 +static_assert(CLI::detail::type_count<std::set<std::string>>::value == 1, "set should have a type size of 1");
  631 +static_assert(CLI::detail::type_count<std::set<std::tuple<std::string, int, int>>>::value == 3,
  632 + "tuple set should have size of 3");
  633 +static_assert(CLI::detail::type_count<std::map<std::string, std::string>>::value == 2,
  634 + "map should have a type size of 2");
  635 +static_assert(CLI::detail::type_count<std::unordered_map<std::string, double>>::value == 2,
  636 + "unordered_map should have a type size of 2");
  637 +
  638 +static_assert(CLI::detail::type_count<std::list<std::pair<int, std::string>>>::value == 2,
  639 + "list<int,string> should have a type size of 2");
  640 +static_assert(CLI::detail::type_count<std::map<std::string, std::pair<int, std::string>>>::value == 3,
  641 + "map<string,pair<int,string>> should have a type size of 3");
  642 +
  643 +template <class T> class TApp_container_single : public TApp {
  644 + public:
  645 + using container_type = T;
  646 + container_type cval{};
  647 + TApp_container_single() : TApp(){};
  648 +};
  649 +
  650 +using containerTypes_single =
  651 + ::testing::Types<std::vector<int>, std::deque<int>, std::set<int>, std::list<int>, std::unordered_set<int>>;
  652 +
  653 +TYPED_TEST_SUITE(TApp_container_single, containerTypes_single, );
  654 +
  655 +TYPED_TEST(TApp_container_single, containerInt) {
  656 +
  657 + auto &cv = TApp_container_single<TypeParam>::cval;
  658 + CLI::Option *opt = (TApp::app).add_option("-v", cv);
  659 +
  660 + TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
  661 + TApp::run();
  662 + EXPECT_EQ(4u, (TApp::app).count("-v"));
  663 + EXPECT_EQ(4u, cv.size());
  664 + opt->check(CLI::PositiveNumber.application_index(0));
  665 + opt->check((!CLI::PositiveNumber).application_index(1));
  666 + EXPECT_NO_THROW(TApp::run());
  667 + EXPECT_EQ(4u, cv.size());
  668 + // v[3] would be negative
  669 + opt->check(CLI::PositiveNumber.application_index(3));
  670 + EXPECT_THROW(TApp::run(), CLI::ValidationError);
  671 +}
  672 +
  673 +template <class T> class TApp_container_pair : public TApp {
  674 + public:
  675 + using container_type = T;
  676 + container_type cval{};
  677 + TApp_container_pair() : TApp(){};
  678 +};
  679 +
  680 +using isp = std::pair<int, std::string>;
  681 +using containerTypes_pair = ::testing::Types<std::vector<isp>,
  682 + std::deque<isp>,
  683 + std::set<isp>,
  684 + std::list<isp>,
  685 + std::map<int, std::string>,
  686 + std::unordered_map<int, std::string>>;
  687 +
  688 +TYPED_TEST_SUITE(TApp_container_pair, containerTypes_pair, );
  689 +
  690 +TYPED_TEST(TApp_container_pair, containerPair) {
  691 +
  692 + auto &cv = TApp_container_pair<TypeParam>::cval;
  693 + (TApp::app).add_option("--dict", cv);
  694 +
  695 + TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"};
  696 +
  697 + TApp::run();
  698 + EXPECT_EQ(cv.size(), 2u);
  699 +
  700 + TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
  701 + TApp::run();
  702 + EXPECT_EQ(cv.size(), 3u);
  703 +}
  704 +
  705 +template <class T> class TApp_container_tuple : public TApp {
  706 + public:
  707 + using container_type = T;
  708 + container_type cval{};
  709 + TApp_container_tuple() : TApp(){};
  710 +};
  711 +
  712 +using tup_obj = std::tuple<int, std::string, double>;
  713 +using containerTypes_tuple = ::testing::Types<std::vector<tup_obj>,
  714 + std::deque<tup_obj>,
  715 + std::set<tup_obj>,
  716 + std::list<tup_obj>,
  717 + std::map<int, std::pair<std::string, double>>,
  718 + std::unordered_map<int, std::tuple<std::string, double>>>;
  719 +
  720 +TYPED_TEST_SUITE(TApp_container_tuple, containerTypes_tuple, );
  721 +
  722 +TYPED_TEST(TApp_container_tuple, containerTuple) {
  723 +
  724 + auto &cv = TApp_container_tuple<TypeParam>::cval;
  725 + (TApp::app).add_option("--dict", cv);
  726 +
  727 + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
  728 +
  729 + TApp::run();
  730 + EXPECT_EQ(cv.size(), 2u);
  731 +
  732 + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
  733 + TApp::run();
  734 + EXPECT_EQ(cv.size(), 3u);
  735 +}
  736 +
  737 +using icontainer1 = std::vector<int>;
  738 +using icontainer2 = std::list<int>;
  739 +using icontainer3 = std::set<int>;
  740 +using icontainer4 = std::pair<int, std::vector<int>>;
  741 +
  742 +using containerTypes_container = ::testing::Types<std::vector<icontainer1>,
  743 + std::list<icontainer1>,
  744 + std::set<icontainer1>,
  745 + std::deque<icontainer1>,
  746 + std::vector<icontainer2>,
  747 + std::list<icontainer2>,
  748 + std::set<icontainer2>,
  749 + std::deque<icontainer2>,
  750 + std::vector<icontainer3>,
  751 + std::list<icontainer3>,
  752 + std::set<icontainer3>,
  753 + std::deque<icontainer3>>;
  754 +
  755 +template <class T> class TApp_container_container : public TApp {
  756 + public:
  757 + using container_type = T;
  758 + container_type cval{};
  759 + TApp_container_container() : TApp(){};
  760 +};
  761 +
  762 +TYPED_TEST_SUITE(TApp_container_container, containerTypes_container, );
  763 +
  764 +TYPED_TEST(TApp_container_container, containerContainer) {
  765 +
  766 + auto &cv = TApp_container_container<TypeParam>::cval;
  767 + (TApp::app).add_option("--dict", cv);
  768 +
  769 + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
  770 +
  771 + TApp::run();
  772 + EXPECT_EQ(cv.size(), 2u);
  773 +
  774 + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
  775 + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
  776 + TApp::run();
  777 + EXPECT_EQ(cv.size(), 4u);
  778 +}
  779 +
  780 +TEST_F(TApp, containerContainer) {
  781 +
  782 + std::vector<icontainer4> cv;
  783 + app.add_option("--dict", cv);
  784 +
  785 + args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
  786 +
  787 + run();
  788 + EXPECT_EQ(cv.size(), 2u);
  789 +
  790 + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "", "--dict",
  791 + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
  792 + run();
  793 + EXPECT_EQ(cv.size(), 4u);
  794 +}
  795 +
  796 +TEST_F(TApp, unknownContainerWrapper) {
  797 +
  798 + class vopt {
  799 + public:
  800 + vopt() = default;
  801 + vopt(const std::vector<double> &vdub) : val_{vdub} {};
  802 + std::vector<double> val_{};
  803 + };
  804 +
  805 + vopt cv;
  806 + app.add_option<vopt, std::vector<double>>("--vv", cv);
  807 +
  808 + args = {"--vv", "1", "2", "4"};
  809 +
  810 + run();
  811 + EXPECT_EQ(cv.val_.size(), 3u);
  812 + args = {"--vv", ""};
  813 +
  814 + run();
  815 + EXPECT_TRUE(cv.val_.empty());
  816 +}
  817 +
  818 +TEST_F(TApp, tupleTwoVectors) {
  819 +
  820 + std::tuple<std::vector<int>, std::vector<int>> cv;
  821 + app.add_option("--vv", cv);
  822 +
  823 + args = {"--vv", "1", "2", "4"};
  824 +
  825 + run();
  826 + EXPECT_EQ(std::get<0>(cv).size(), 3U);
  827 + EXPECT_TRUE(std::get<1>(cv).empty());
  828 +
  829 + args = {"--vv", "1", "2", "%%", "4", "4", "5"};
  830 +
  831 + run();
  832 + EXPECT_EQ(std::get<0>(cv).size(), 2U);
  833 + EXPECT_EQ(std::get<1>(cv).size(), 3U);
  834 +}
... ...
tests/OptionalTest.cpp
  1 +#include <complex>
1 2 #include <cstdint>
2 3 #include <cstdlib>
3 4 #include <iostream>
... ... @@ -72,6 +73,44 @@ TEST_F(TApp, StdOptionalTest) {
72 73 EXPECT_EQ(*opt, 3);
73 74 }
74 75  
  76 +TEST_F(TApp, StdOptionalVectorEmptyDirect) {
  77 + std::optional<std::vector<int>> opt;
  78 + app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
  79 + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
  80 + run();
  81 + EXPECT_FALSE(opt);
  82 + args = {"-v"};
  83 + opt = std::vector<int>{4, 3};
  84 + run();
  85 + EXPECT_FALSE(opt);
  86 + args = {"-v", "1", "4", "5"};
  87 + run();
  88 + EXPECT_TRUE(opt);
  89 + std::vector<int> expV{1, 4, 5};
  90 + EXPECT_EQ(*opt, expV);
  91 +}
  92 +
  93 +TEST_F(TApp, StdOptionalComplexDirect) {
  94 + std::optional<std::complex<double>> opt;
  95 + app.add_option("-c,--complex", opt)->type_size(0, 2);
  96 + run();
  97 + EXPECT_FALSE(opt);
  98 + args = {"-c"};
  99 + opt = std::complex<double>{4.0, 3.0};
  100 + run();
  101 + EXPECT_FALSE(opt);
  102 + args = {"-c", "1+2j"};
  103 + run();
  104 + EXPECT_TRUE(opt);
  105 + std::complex<double> val{1, 2};
  106 + EXPECT_EQ(*opt, val);
  107 + args = {"-c", "3", "-4"};
  108 + run();
  109 + EXPECT_TRUE(opt);
  110 + std::complex<double> val2{3, -4};
  111 + EXPECT_EQ(*opt, val2);
  112 +}
  113 +
75 114 #ifdef _MSC_VER
76 115 #pragma warning(default : 4244)
77 116 #endif
... ... @@ -165,11 +204,16 @@ TEST_F(TApp, BoostOptionalStringTest) {
165 204 EXPECT_TRUE(opt);
166 205 EXPECT_EQ(*opt, "strv");
167 206 }
  207 +namespace boost {
  208 +using CLI::enums::operator<<;
  209 +}
168 210  
169 211 TEST_F(TApp, BoostOptionalEnumTest) {
  212 +
170 213 enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 };
171   - boost::optional<eval> opt;
  214 + boost::optional<eval> opt, opt2;
172 215 auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt);
  216 + app.add_option_no_stream("-e,--eval", opt2);
173 217 optptr->capture_default_str();
174 218  
175 219 auto dstring = optptr->get_default_str();
... ... @@ -206,6 +250,7 @@ TEST_F(TApp, BoostOptionalVector) {
206 250 TEST_F(TApp, BoostOptionalVectorEmpty) {
207 251 boost::optional<std::vector<int>> opt;
208 252 app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
  253 + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
209 254 run();
210 255 EXPECT_FALSE(opt);
211 256 args = {"-v"};
... ... @@ -219,6 +264,44 @@ TEST_F(TApp, BoostOptionalVectorEmpty) {
219 264 EXPECT_EQ(*opt, expV);
220 265 }
221 266  
  267 +TEST_F(TApp, BoostOptionalVectorEmptyDirect) {
  268 + boost::optional<std::vector<int>> opt;
  269 + app.add_option_no_stream("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
  270 + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
  271 + run();
  272 + EXPECT_FALSE(opt);
  273 + args = {"-v"};
  274 + opt = std::vector<int>{4, 3};
  275 + run();
  276 + EXPECT_FALSE(opt);
  277 + args = {"-v", "1", "4", "5"};
  278 + run();
  279 + EXPECT_TRUE(opt);
  280 + std::vector<int> expV{1, 4, 5};
  281 + EXPECT_EQ(*opt, expV);
  282 +}
  283 +
  284 +TEST_F(TApp, BoostOptionalComplexDirect) {
  285 + boost::optional<std::complex<double>> opt;
  286 + app.add_option("-c,--complex", opt)->type_size(0, 2);
  287 + run();
  288 + EXPECT_FALSE(opt);
  289 + args = {"-c"};
  290 + opt = std::complex<double>{4.0, 3.0};
  291 + run();
  292 + EXPECT_FALSE(opt);
  293 + args = {"-c", "1+2j"};
  294 + run();
  295 + EXPECT_TRUE(opt);
  296 + std::complex<double> val{1, 2};
  297 + EXPECT_EQ(*opt, val);
  298 + args = {"-c", "3", "-4"};
  299 + run();
  300 + EXPECT_TRUE(opt);
  301 + std::complex<double> val2{3, -4};
  302 + EXPECT_EQ(*opt, val2);
  303 +}
  304 +
222 305 #endif
223 306  
224 307 #if !CLI11_OPTIONAL
... ...
tests/TrueFalseTest.cpp
1 1 #include "app_helper.hpp"
2 2  
3 3 /// This allows a set of strings to be run over by a test
4   -struct TApp_TBO : public TApp, public ::testing::WithParamInterface<const char *> {};
  4 +struct TApp_TBO : public TApp_base, testing::TestWithParam<const char *> {};
5 5  
6 6 TEST_P(TApp_TBO, TrueBoolOption) {
7 7 bool value{false}; // Not used, but set just in case
... ... @@ -13,10 +13,10 @@ TEST_P(TApp_TBO, TrueBoolOption) {
13 13 }
14 14  
15 15 // Change to INSTANTIATE_TEST_SUITE_P in GTest master
16   -INSTANTIATE_TEST_CASE_P(TrueBoolOptions, TApp_TBO, ::testing::Values("true", "on", "True", "ON"), );
  16 +INSTANTIATE_TEST_SUITE_P(TrueBoolOptions_test, TApp_TBO, testing::Values("true", "on", "True", "ON"));
17 17  
18 18 /// This allows a set of strings to be run over by a test
19   -struct TApp_FBO : public TApp, public ::testing::WithParamInterface<const char *> {};
  19 +struct TApp_FBO : public TApp_base, public ::testing::TestWithParam<const char *> {};
20 20  
21 21 TEST_P(TApp_FBO, FalseBoolOptions) {
22 22 bool value{true}; // Not used, but set just in case
... ... @@ -27,4 +27,4 @@ TEST_P(TApp_FBO, FalseBoolOptions) {
27 27 EXPECT_FALSE(value);
28 28 }
29 29  
30   -INSTANTIATE_TEST_CASE_P(FalseBoolOptions, TApp_FBO, ::testing::Values("false", "off", "False", "OFF"), );
  30 +INSTANTIATE_TEST_SUITE_P(FalseBoolOptions_test, TApp_FBO, ::testing::Values("false", "off", "False", "OFF"));
... ...
tests/app_helper.hpp
... ... @@ -11,10 +11,11 @@
11 11  
12 12 using input_t = std::vector<std::string>;
13 13  
14   -struct TApp : public ::testing::Test {
  14 +class TApp_base {
  15 + public:
15 16 CLI::App app{"My Test Program"};
16 17 input_t args{};
17   -
  18 + virtual ~TApp_base() = default;
18 19 void run() {
19 20 // It is okay to re-parse - clear is called automatically before a parse.
20 21 input_t newargs = args;
... ... @@ -23,6 +24,8 @@ struct TApp : public ::testing::Test {
23 24 }
24 25 };
25 26  
  27 +class TApp : public TApp_base, public ::testing::Test {};
  28 +
26 29 class TempFile {
27 30 std::string _name{};
28 31  
... ...