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,14 +195,15 @@ While all options internally are the same type, there are several ways to add an
195 app.add_option(option_name, help_str="") 195 app.add_option(option_name, help_str="")
196 196
197 app.add_option(option_name, 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 help_string="") 199 help_string="")
200 200
201 app.add_option_function<type>(option_name, 201 app.add_option_function<type>(option_name,
202 function <void(const type &value)>, // type can be any type supported by add_option 202 function <void(const type &value)>, // type can be any type supported by add_option
203 help_string="") 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 // πŸ†• 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 // πŸ†• 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 app.add_option<typename T, typename XC>(option_name, 208 app.add_option<typename T, typename XC>(option_name,
208 T &output, // output must be assignable or constructible from a value of type XC 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,7 +214,7 @@ app.add_flag(option_name,
213 help_string="") 214 help_string="")
214 215
215 app.add_flag(option_name, 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 help_string="") 218 help_string="")
218 219
219 app.add_flag_function(option_name, 220 app.add_flag_function(option_name,
@@ -245,9 +246,9 @@ app.add_option&lt;vtype,std:string&gt;(&quot;--vs&quot;,v1); @@ -245,9 +246,9 @@ app.add_option&lt;vtype,std:string&gt;(&quot;--vs&quot;,v1);
245 app.add_option<vtype,int>("--vi",v1); 246 app.add_option<vtype,int>("--vi",v1);
246 app.add_option<vtype,double>("--vf",v1); 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 Vector types can also be used in the two parameter template overload 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,7 +309,7 @@ Before parsing, you can set the following options:
308 - `->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 - `->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 - `->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 - `->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 - `->description(str)`: Set/change the description. 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 - `->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 - `->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 - `->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 - `->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 - `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. 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,7 +320,7 @@ Before parsing, you can set the following options:
319 - `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses. 320 - `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses.
320 - `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. 321 - `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
321 - `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_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 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. 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,7 +9,7 @@ int int_option{0};
9 app.add_option("-i", int_option, "Optional description"); 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 ```cpp 14 ```cpp
15 int int_option{0}; 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,11 +20,15 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi
20 20
21 | Type | CLI11 | 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 | 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. (`{}`) | 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 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. 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,15 +50,15 @@ To make a positional option, you simply give CLI11 one name that does not start
46 50
47 This would make two short option aliases, two long option alias, and the option would be also be accepted as a positional. 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 | Separate names | Combined names | 57 | Separate names | Combined names |
54 |-------------------|-----------------| 58 |-------------------|-----------------|
55 | `--vec 1 --vec 2` | `--vec 1 2` | 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 An example of setting up a vector option: 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,6 +69,43 @@ app.add_option(&quot;--vec&quot;, int_vec, &quot;My vector option&quot;);
65 69
66 Vectors will be replaced by the parsed content if the option is given on the command line. 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 ## Option modifiers 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,25 +116,32 @@ When you call `add_option`, you get a pointer to the added option. You can use t
75 | `->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. | 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 | `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. | 117 | `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. |
77 | `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. | 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 | `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. | 121 | `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. |
79 | `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. | 122 | `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. |
80 | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. | 123 | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. |
81 | `->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. | 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 | `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). | 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 | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. | 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 | `->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()`. | 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 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | 133 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
  134 +| `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` |
88 | `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines or the specified delimiter to join all arguments into a single string output. | 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 | `->each(void(std::string))` | Run a function on each parsed value, *in order*. | 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 ## Using the `CLI::Option` pointer 146 ## Using the `CLI::Option` pointer
99 147
@@ -110,12 +158,17 @@ if(* opt) @@ -110,12 +158,17 @@ if(* opt)
110 158
111 ## Inheritance of defaults 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 * `group`: The group name starts as "Options" 163 * `group`: The group name starts as "Options"
116 * `required`: If the option must be given. Defaults to `false`. Is ignored for flags. 164 * `required`: If the option must be given. Defaults to `false`. Is ignored for flags.
117 * `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. 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 * `ignore_case`: Allow any mixture of cases for the option or flag name 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 An example of usage: 173 An example of usage:
121 174
@@ -129,29 +182,7 @@ app.get_group() // is &quot;Required&quot; @@ -129,29 +182,7 @@ app.get_group() // is &quot;Required&quot;
129 Groups are mostly for visual organization, but an empty string for a group name will hide the option. 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 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: 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,12 +191,13 @@ You can also set the app setting `app-&gt;allow_windows_style_options()` to allow w
160 * `/long` (long flag) 191 * `/long` (long flag)
161 * `/file filename` (space) 192 * `/file filename` (space)
162 * `/file:filename` (colon) 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 ## Parse configuration 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 ### examples 202 ### examples
171 How options manage this is best illustrated through some examples 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,7 +205,7 @@ How options manage this is best illustrated through some examples
173 std::string val; 205 std::string val;
174 app.add_option("--opt",val,"description"); 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 now for example 210 now for example
179 ```cpp 211 ```cpp
@@ -203,6 +235,20 @@ app.add_flag(&quot;--opt&quot;,val,&quot;description&quot;); @@ -203,6 +235,20 @@ app.add_flag(&quot;--opt&quot;,val,&quot;description&quot;);
203 235
204 Using the add_flag methods for creating options creates an option with an expected size of 0, implying no arguments can be passed. 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 ### Customization 252 ### Customization
207 253
208 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 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,5 +260,9 @@ opt-&gt;expected(0,1);
214 ``` 260 ```
215 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. 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,21 +610,39 @@ class App {
610 // to structs used in the evaluation can be temporary so that would cause issues. 610 // to structs used in the evaluation can be temporary so that would cause issues.
611 auto Tcount = detail::type_count<AssignTo>::value; 611 auto Tcount = detail::type_count<AssignTo>::value;
612 auto XCcount = detail::type_count<ConvertTo>::value; 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 opt->expected(detail::expected_count<ConvertTo>::value); 614 opt->expected(detail::expected_count<ConvertTo>::value);
615 opt->run_callback_for_default(); 615 opt->run_callback_for_default();
616 return opt; 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 /// Add option for a callback of a specific type 637 /// Add option for a callback of a specific type
620 - template <typename T> 638 + template <typename ArgType>
621 Option *add_option_function(std::string option_name, 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 std::string option_description = "") { 641 std::string option_description = "") {
624 642
625 auto fun = [func](const CLI::results_t &res) { 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 if(result) { 646 if(result) {
629 func(variable); 647 func(variable);
630 } 648 }
@@ -632,15 +650,15 @@ class App { @@ -632,15 +650,15 @@ class App {
632 }; 650 };
633 651
634 Option *opt = add_option(option_name, std::move(fun), option_description, false); 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 return opt; 656 return opt;
639 } 657 }
640 658
641 /// Add option with no description or variable assignment 659 /// Add option with no description or variable assignment
642 Option *add_option(std::string option_name) { 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 /// Add option with description but with no variable assignment or callback 664 /// Add option with description but with no variable assignment or callback
@@ -749,7 +767,7 @@ class App { @@ -749,7 +767,7 @@ class App {
749 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes 767 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
750 /// that can be converted from a string 768 /// that can be converted from a string
751 template <typename T, 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 (!std::is_integral<T>::value || is_bool<T>::value) && 771 (!std::is_integral<T>::value || is_bool<T>::value) &&
754 !std::is_constructible<std::function<void(int)>, T>::value, 772 !std::is_constructible<std::function<void(int)>, T>::value,
755 detail::enabler> = detail::dummy> 773 detail::enabler> = detail::dummy>
@@ -873,7 +891,7 @@ class App { @@ -873,7 +891,7 @@ class App {
873 return opt; 891 return opt;
874 } 892 }
875 893
876 - /// Add a complex number 894 + /// Add a complex number DEPRECATED --use add_option instead
877 template <typename T, typename XC = double> 895 template <typename T, typename XC = double>
878 Option *add_complex(std::string option_name, 896 Option *add_complex(std::string option_name,
879 T &variable, 897 T &variable,
@@ -2660,7 +2678,12 @@ class App { @@ -2660,7 +2678,12 @@ class App {
2660 2678
2661 // Get a reference to the pointer to make syntax bearable 2679 // Get a reference to the pointer to make syntax bearable
2662 Option_p &op = *op_ptr; 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 int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min()); 2687 int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
2665 int max_num = op->get_items_expected_max(); 2688 int max_num = op->get_items_expected_max();
2666 2689
@@ -2725,7 +2748,7 @@ class App { @@ -2725,7 +2748,7 @@ class App {
2725 } 2748 }
2726 2749
2727 // if we only partially completed a type then add an empty string for later processing 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 op->add_result(std::string{}); 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,7 +315,7 @@ class Option : public OptionBase&lt;Option&gt; {
315 /// results after reduction 315 /// results after reduction
316 results_t proc_results_{}; 316 results_t proc_results_{};
317 /// enumeration for the option state machine 317 /// enumeration for the option state machine
318 - enum class option_state { 318 + enum class option_state : char {
319 parsing = 0, //!< The option is currently collecting parsed results 319 parsing = 0, //!< The option is currently collecting parsed results
320 validated = 2, //!< the results have been validated 320 validated = 2, //!< the results have been validated
321 reduced = 4, //!< a subset of results has been generated 321 reduced = 4, //!< a subset of results has been generated
@@ -329,6 +329,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -329,6 +329,8 @@ class Option : public OptionBase&lt;Option&gt; {
329 bool flag_like_{false}; 329 bool flag_like_{false};
330 /// Control option to run the callback to set the default 330 /// Control option to run the callback to set the default
331 bool run_callback_for_default_{false}; 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 /// Making an option by hand is not defined, it must be made by the App class 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,6 +660,9 @@ class Option : public OptionBase&lt;Option&gt; {
658 /// The maximum number of arguments the option expects 660 /// The maximum number of arguments the option expects
659 int get_type_size_max() const { return type_size_max_; } 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 /// The environment variable associated to this value 666 /// The environment variable associated to this value
662 std::string get_envname() const { return envname_; } 667 std::string get_envname() const { return envname_; }
663 668
@@ -963,8 +968,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -963,8 +968,8 @@ class Option : public OptionBase&lt;Option&gt; {
963 return this; 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 /// Get a copy of the results 974 /// Get a copy of the results
970 results_t reduced_results() const { 975 results_t reduced_results() const {
@@ -986,8 +991,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -986,8 +991,7 @@ class Option : public OptionBase&lt;Option&gt; {
986 } 991 }
987 992
988 /// Get the results as a specified type 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 bool retval; 995 bool retval;
992 if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) { 996 if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
993 const results_t &res = (proc_results_.empty()) ? results_ : proc_results_; 997 const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
@@ -1054,6 +1058,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1054,6 +1058,8 @@ class Option : public OptionBase&lt;Option&gt; {
1054 type_size_max_ = option_type_size; 1058 type_size_max_ = option_type_size;
1055 if(type_size_max_ < detail::expected_max_vector_size) { 1059 if(type_size_max_ < detail::expected_max_vector_size) {
1056 type_size_min_ = option_type_size; 1060 type_size_min_ = option_type_size;
  1061 + } else {
  1062 + inject_separator_ = true;
1057 } 1063 }
1058 if(type_size_max_ == 0) 1064 if(type_size_max_ == 0)
1059 required_ = false; 1065 required_ = false;
@@ -1079,9 +1085,15 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1079,9 +1085,15 @@ class Option : public OptionBase&lt;Option&gt; {
1079 if(type_size_max_ == 0) { 1085 if(type_size_max_ == 0) {
1080 required_ = false; 1086 required_ = false;
1081 } 1087 }
  1088 + if(type_size_max_ >= detail::expected_max_vector_size) {
  1089 + inject_separator_ = true;
  1090 + }
1082 return this; 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 /// Set a capture function for the default. Mostly used by App. 1097 /// Set a capture function for the default. Mostly used by App.
1086 Option *default_function(const std::function<std::string()> &func) { 1098 Option *default_function(const std::function<std::string()> &func) {
1087 default_function_ = func; 1099 default_function_ = func;
@@ -1157,7 +1169,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1157,7 +1169,7 @@ class Option : public OptionBase&lt;Option&gt; {
1157 } 1169 }
1158 1170
1159 for(std::string &result : res) { 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 index = 0; // reset index for variable size chunks 1173 index = 0; // reset index for variable size chunks
1162 continue; 1174 continue;
1163 } 1175 }
include/CLI/StringTools.hpp
@@ -190,6 +190,12 @@ inline bool valid_name_string(const std::string &amp;str) { @@ -190,6 +190,12 @@ inline bool valid_name_string(const std::string &amp;str) {
190 return true; 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 /// Verify that str consists of letters only 199 /// Verify that str consists of letters only
194 inline bool isalpha(const std::string &str) { 200 inline bool isalpha(const std::string &str) {
195 return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); 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,15 +45,6 @@ template &lt;typename... Ts&gt; using void_t = typename make_void&lt;Ts...&gt;::type;
45 /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine 45 /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine
46 template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type; 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 /// Check to see if something is bool (fail check by default) 48 /// Check to see if something is bool (fail check by default)
58 template <typename T> struct is_bool : std::false_type {}; 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,6 +186,17 @@ template &lt;typename T, typename S = std::istringstream&gt; class is_istreamable {
195 static constexpr bool value = decltype(test<T, S>(0))::value; 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 /// Templated operation to get a value from a stream 200 /// Templated operation to get a value from a stream
199 template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy> 201 template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
200 bool from_stream(const std::string &istring, T &obj) { 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,12 +211,49 @@ bool from_stream(const std::string &amp; /*istring*/, T &amp; /*obj*/) {
209 return false; 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 // Check for tuple like types, as in classes with a tuple_size type trait 251 // Check for tuple like types, as in classes with a tuple_size type trait
213 template <typename S> class is_tuple_like { 252 template <typename S> class is_tuple_like {
214 template <typename SS> 253 template <typename SS>
215 // static auto test(int) 254 // static auto test(int)
216 // -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type()); 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 template <typename> static auto test(...) -> std::false_type; 257 template <typename> static auto test(...) -> std::false_type;
219 258
220 public: 259 public:
@@ -249,20 +288,19 @@ std::string to_string(T &amp;&amp;value) { @@ -249,20 +288,19 @@ std::string to_string(T &amp;&amp;value) {
249 /// If conversion is not supported, return an empty string (streaming is not supported for that type) 288 /// If conversion is not supported, return an empty string (streaming is not supported for that type)
250 template <typename T, 289 template <typename T,
251 enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value && 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 detail::enabler> = detail::dummy> 292 detail::enabler> = detail::dummy>
254 std::string to_string(T &&) { 293 std::string to_string(T &&) {
255 return std::string{}; 294 return std::string{};
256 } 295 }
257 296
258 -/// convert a vector to a string 297 +/// convert a readable container to a string
259 template <typename T, 298 template <typename T,
260 enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value && 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 detail::enabler> = detail::dummy> 301 detail::enabler> = detail::dummy>
263 std::string to_string(T &&variable) { 302 std::string to_string(T &&variable) {
264 std::vector<std::string> defaults; 303 std::vector<std::string> defaults;
265 - defaults.reserve(variable.size());  
266 auto cval = variable.begin(); 304 auto cval = variable.begin();
267 auto end = variable.end(); 305 auto end = variable.end();
268 while(cval != end) { 306 while(cval != end) {
@@ -306,25 +344,142 @@ auto value_string(const T &amp;value) -&gt; decltype(to_string(value)) { @@ -306,25 +344,142 @@ auto value_string(const T &amp;value) -&gt; decltype(to_string(value)) {
306 return to_string(value); 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 /// This will only trigger for actual void type 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 /// Set of overloads to get the type size of an object 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 template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> { 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 /// Type size for regular object types that do not look like a tuple 439 /// Type size for regular object types that do not look like a tuple
317 template <typename T> 440 template <typename T>
318 -struct type_count< 441 +struct type_count_min<
319 T, 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 static constexpr int value{1}; 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 /// This will only trigger for actual void type 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,14 +487,22 @@ template &lt;typename T, typename Enable = void&gt; struct expected_count { static con
332 487
333 /// For most types the number of expected items is 1 488 /// For most types the number of expected items is 1
334 template <typename T> 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 static constexpr int value{1}; 493 static constexpr int value{1};
337 }; 494 };
338 /// number of expected items in a vector 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 static constexpr int value{expected_max_vector_size}; 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 // Enumeration of the different supported categorizations of objects 506 // Enumeration of the different supported categorizations of objects
344 enum class object_category : int { 507 enum class object_category : int {
345 integral_value = 2, 508 integral_value = 2,
@@ -350,12 +513,15 @@ enum class object_category : int { @@ -350,12 +513,15 @@ enum class object_category : int {
350 number_constructible = 12, 513 number_constructible = 12,
351 double_constructible = 14, 514 double_constructible = 14,
352 integer_constructible = 16, 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,10 +558,9 @@ template &lt;typename T&gt; struct classify_object&lt;T, typename std::enable_if&lt;std::is_
392 558
393 /// String and similar direct assignment 559 /// String and similar direct assignment
394 template <typename T> 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 static constexpr object_category value{object_category::string_assignable}; 564 static constexpr object_category value{object_category::string_assignable};
400 }; 565 };
401 566
@@ -404,8 +569,8 @@ template &lt;typename T&gt; @@ -404,8 +569,8 @@ template &lt;typename T&gt;
404 struct classify_object< 569 struct classify_object<
405 T, 570 T,
406 typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && 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 static constexpr object_category value{object_category::string_constructible}; 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,23 +579,35 @@ template &lt;typename T&gt; struct classify_object&lt;T, typename std::enable_if&lt;std::is_
414 static constexpr object_category value{object_category::enumeration}; 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 /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, 586 /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point,
418 /// vectors, and enumerations 587 /// vectors, and enumerations
419 template <typename T> struct uncommon_type { 588 template <typename T> struct uncommon_type {
420 using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value && 589 using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
421 !std::is_assignable<T &, std::string>::value && 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 std::true_type, 593 std::true_type,
425 std::false_type>::type; 594 std::false_type>::type;
426 static constexpr bool value = type::value; 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 /// Assignable from double or int 606 /// Assignable from double or int
430 template <typename T> 607 template <typename T>
431 struct classify_object<T, 608 struct classify_object<T,
432 typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && 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 is_direct_constructible<T, int>::value>::type> { 611 is_direct_constructible<T, int>::value>::type> {
435 static constexpr object_category value{object_category::number_constructible}; 612 static constexpr object_category value{object_category::number_constructible};
436 }; 613 };
@@ -439,7 +616,7 @@ struct classify_object&lt;T, @@ -439,7 +616,7 @@ struct classify_object&lt;T,
439 template <typename T> 616 template <typename T>
440 struct classify_object<T, 617 struct classify_object<T,
441 typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && 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 is_direct_constructible<T, int>::value>::type> { 620 is_direct_constructible<T, int>::value>::type> {
444 static constexpr object_category value{object_category::integer_constructible}; 621 static constexpr object_category value{object_category::integer_constructible};
445 }; 622 };
@@ -448,24 +625,30 @@ struct classify_object&lt;T, @@ -448,24 +625,30 @@ struct classify_object&lt;T,
448 template <typename T> 625 template <typename T>
449 struct classify_object<T, 626 struct classify_object<T,
450 typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && 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 !is_direct_constructible<T, int>::value>::type> { 629 !is_direct_constructible<T, int>::value>::type> {
453 static constexpr object_category value{object_category::double_constructible}; 630 static constexpr object_category value{object_category::double_constructible};
454 }; 631 };
455 632
456 /// Tuple type 633 /// Tuple type
457 template <typename T> 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 static constexpr object_category value{object_category::tuple_value}; 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 // Type name print 654 // Type name print
@@ -511,31 +694,53 @@ constexpr const char *type_name() { @@ -511,31 +694,53 @@ constexpr const char *type_name() {
511 return "BOOLEAN"; 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 /// Print for all other types 704 /// Print for all other types
515 template <typename T, 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 constexpr const char *type_name() { 709 constexpr const char *type_name() {
518 return "TEXT"; 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 /// Print name for single element tuple types 725 /// Print name for single element tuple types
522 template <typename T, 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 detail::enabler> = detail::dummy> 728 detail::enabler> = detail::dummy>
525 inline std::string type_name() { 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 /// Empty string if the index > tuple size 733 /// Empty string if the index > tuple size
530 template <typename T, std::size_t I> 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 return std::string{}; 736 return std::string{};
533 } 737 }
534 738
535 /// Recursively generate the tuple type name 739 /// Recursively generate the tuple type name
536 template <typename T, std::size_t I> 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 if(str.back() == ',') 744 if(str.back() == ',')
540 str.pop_back(); 745 str.pop_back();
541 return str; 746 return str;
@@ -543,17 +748,19 @@ template &lt;typename T, std::size_t I&gt; @@ -543,17 +748,19 @@ template &lt;typename T, std::size_t I&gt;
543 748
544 /// Print type name for tuples with 2 or more elements 749 /// Print type name for tuples with 2 or more elements
545 template <typename T, 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 auto tname = std::string(1, '[') + tuple_name<T, 0>(); 754 auto tname = std::string(1, '[') + tuple_name<T, 0>();
550 tname.push_back(']'); 755 tname.push_back(']');
551 return tname; 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 template <typename T, 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 inline std::string type_name() { 764 inline std::string type_name() {
558 return type_name<typename T::value_type>(); 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,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 /// String and similar direct assignment 913 /// String and similar direct assignment
675 template <typename T, 914 template <typename T,
676 enable_if_t<classify_object<T>::value == object_category::string_assignable, detail::enabler> = detail::dummy> 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,6 +940,18 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
701 return true; 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 /// Assignable from double or int 955 /// Assignable from double or int
705 template < 956 template <
706 typename T, 957 typename T,
@@ -756,38 +1007,40 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) { @@ -756,38 +1007,40 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
756 } 1007 }
757 1008
758 /// Assign a value through lexical cast operations 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 return lexical_cast(input, output); 1018 return lexical_cast(input, output);
767 } 1019 }
768 1020
769 /// Assign a value through lexical cast operations 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 detail::enabler> = detail::dummy> 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 if(input.empty()) { 1029 if(input.empty()) {
777 - output = T{}; 1030 + output = AssignTo{};
778 return true; 1031 return true;
779 } 1032 }
780 return lexical_cast(input, output); 1033 return lexical_cast(input, output);
781 } 1034 }
782 1035
783 /// Assign a value converted from a string in lexical cast to the output value directly 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 if(parse_result) { 1044 if(parse_result) {
792 output = val; 1045 output = val;
793 } 1046 }
@@ -795,195 +1048,325 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) { @@ -795,195 +1048,325 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) {
795 } 1048 }
796 1049
797 /// Assign a value from a lexical cast through constructing a value and move assigning it 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 if(parse_result) { 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 return parse_result; 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 bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1); 1087 bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1);
829 if(strings.size() > 1) { 1088 if(strings.size() > 1) {
830 retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2); 1089 retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2);
831 } 1090 }
832 if(retval) { 1091 if(retval) {
833 - output = T{v1, v2}; 1092 + output = AssignTo{v1, v2};
834 } 1093 }
835 return retval; 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 detail::enabler> = detail::dummy> 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 for(const auto &elem : strings) { 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 if(!retval) { 1108 if(!retval) {
852 return false; 1109 return false;
853 } 1110 }
  1111 + output.insert(output.end(), std::move(out));
854 } 1112 }
855 return (!output.empty()); 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 /// Conversion to a vector type using a particular single type as the conversion type 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 detail::enabler> = detail::dummy> 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 bool retval = true; 1144 bool retval = true;
891 output.clear(); 1145 output.clear();
892 output.reserve(strings.size()); 1146 output.reserve(strings.size());
893 for(const auto &elem : strings) { 1147 for(const auto &elem : strings) {
894 1148
895 output.emplace_back(); 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 return (!output.empty()) && retval; 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 if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { 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 return retval; 1198 return retval;
913 } 1199 }
914 - output = T{}; 1200 + output = AssignTo{};
915 return true; 1201 return true;
916 } 1202 }
917 1203
918 /// function template for converting tuples if the static Index is greater than the tuple size 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 return true; 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 /// Tuple conversion operation 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 bool retval = true; 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 return retval; 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 static_assert( 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 "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); 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 bool retval = true; 1317 bool retval = true;
956 output.clear(); 1318 output.clear();
957 std::vector<std::string> temp; 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 temp.push_back(strings[ii]); 1325 temp.push_back(strings[ii]);
963 ++ii; 1326 ++ii;
964 ++icount; 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 temp.pop_back(); 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 temp.clear(); 1335 temp.clear();
972 if(!retval) { 1336 if(!retval) {
973 return false; 1337 return false;
974 } 1338 }
  1339 + output.insert(output.end(), std::move(temp_out));
975 icount = 0; 1340 icount = 0;
976 } 1341 }
977 } 1342 }
978 return retval; 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 /// Sum a vector of flag representations 1364 /// Sum a vector of flag representations
981 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is 1365 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
982 /// by 1366 /// by
983 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most 1367 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
984 /// common true and false strings then uses stoll to convert the rest for summing 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 void sum_flag_vector(const std::vector<std::string> &flags, T &output) { 1370 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
988 std::int64_t count{0}; 1371 std::int64_t count{0};
989 for(auto &flag : flags) { 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,8 +1380,7 @@ void sum_flag_vector(const std::vector&lt;std::string&gt; &amp;flags, T &amp;output) {
997 /// by 1380 /// by
998 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most 1381 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
999 /// common true and false strings then uses stoll to convert the rest for summing 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 void sum_flag_vector(const std::vector<std::string> &flags, T &output) { 1384 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
1003 std::int64_t count{0}; 1385 std::int64_t count{0};
1004 for(auto &flag : flags) { 1386 for(auto &flag : flags) {
tests/AppTest.cpp
@@ -454,98 +454,6 @@ TEST_F(TApp, SepInt) { @@ -454,98 +454,6 @@ TEST_F(TApp, SepInt) {
454 EXPECT_EQ(i, 4); 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 TEST_F(TApp, DefaultStringAgain) { 457 TEST_F(TApp, DefaultStringAgain) {
550 std::string str = "previous"; 458 std::string str = "previous";
551 app.add_option("-s,--string", str); 459 app.add_option("-s,--string", str);
@@ -647,35 +555,6 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) { @@ -647,35 +555,6 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) {
647 EXPECT_EQ(1u, app.count("-A")); 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 TEST_F(TApp, FlagLikeOption) { 558 TEST_F(TApp, FlagLikeOption) {
680 bool val{false}; 559 bool val{false};
681 auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true"); 560 auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
@@ -724,32 +603,6 @@ TEST_F(TApp, BoolOnlyFlag) { @@ -724,32 +603,6 @@ TEST_F(TApp, BoolOnlyFlag) {
724 EXPECT_THROW(run(), CLI::ArgumentMismatch); 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 TEST_F(TApp, ShortOpts) { 606 TEST_F(TApp, ShortOpts) {
754 607
755 unsigned long long funnyint{0}; 608 unsigned long long funnyint{0};
@@ -892,37 +745,6 @@ TEST_F(TApp, TakeLastOptMulti) { @@ -892,37 +745,6 @@ TEST_F(TApp, TakeLastOptMulti) {
892 EXPECT_EQ(vals, std::vector<int>({2, 3})); 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 TEST_F(TApp, TakeLastOptMulti_alternative_path) { 748 TEST_F(TApp, TakeLastOptMulti_alternative_path) {
927 std::vector<int> vals; 749 std::vector<int> vals;
928 app.add_option("--long", vals)->expected(2, -1)->take_last(); 750 app.add_option("--long", vals)->expected(2, -1)->take_last();
@@ -1452,27 +1274,6 @@ TEST_F(TApp, CallbackFlags) { @@ -1452,27 +1274,6 @@ TEST_F(TApp, CallbackFlags) {
1452 EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction); 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 TEST_F(TApp, CallbackFlagsFalse) { 1277 TEST_F(TApp, CallbackFlagsFalse) {
1477 std::int64_t value = 0; 1278 std::int64_t value = 0;
1478 1279
@@ -1747,107 +1548,6 @@ TEST_F(TApp, NotFileExists) { @@ -1747,107 +1548,6 @@ TEST_F(TApp, NotFileExists) {
1747 EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); 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 TEST_F(TApp, DefaultedResult) { 1551 TEST_F(TApp, DefaultedResult) {
1852 std::string sval = "NA"; 1552 std::string sval = "NA";
1853 int ival{0}; 1553 int ival{0};
@@ -1866,93 +1566,6 @@ TEST_F(TApp, DefaultedResult) { @@ -1866,93 +1566,6 @@ TEST_F(TApp, DefaultedResult) {
1866 EXPECT_EQ(newIval, 442); 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 TEST_F(TApp, OriginalOrder) { 1569 TEST_F(TApp, OriginalOrder) {
1957 std::vector<int> st1; 1570 std::vector<int> st1;
1958 CLI::Option *op1 = app.add_option("-a", st1); 1571 CLI::Option *op1 = app.add_option("-a", st1);
@@ -2406,170 +2019,6 @@ TEST_F(TApp, EachItem) { @@ -2406,170 +2019,6 @@ TEST_F(TApp, EachItem) {
2406 EXPECT_EQ(results, dummy); 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 // #128 2022 // #128
2574 TEST_F(TApp, RepeatingMultiArgumentOptions) { 2023 TEST_F(TApp, RepeatingMultiArgumentOptions) {
2575 std::vector<std::string> entries; 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,13 +32,19 @@ endif()
32 set(GOOGLE_TEST_INDIVIDUAL OFF) 32 set(GOOGLE_TEST_INDIVIDUAL OFF)
33 include(AddGoogletest) 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 set(CLI11_TESTS 40 set(CLI11_TESTS
36 HelpersTest 41 HelpersTest
37 ConfigFileTest 42 ConfigFileTest
  43 + OptionTypeTest
38 SimpleTest 44 SimpleTest
39 AppTest 45 AppTest
40 SetTest 46 SetTest
41 - TransformTest 47 + TransformTest
42 CreationTest 48 CreationTest
43 SubcommandTest 49 SubcommandTest
44 HelpTest 50 HelpTest
@@ -47,6 +53,7 @@ set(CLI11_TESTS @@ -47,6 +53,7 @@ set(CLI11_TESTS
47 OptionalTest 53 OptionalTest
48 DeprecatedTest 54 DeprecatedTest
49 StringParseTest 55 StringParseTest
  56 + ComplexTypeTest
50 TrueFalseTest 57 TrueFalseTest
51 OptionGroupTest 58 OptionGroupTest
52 ) 59 )
@@ -55,6 +62,10 @@ if(WIN32) @@ -55,6 +62,10 @@ if(WIN32)
55 list(APPEND CLI11_TESTS WindowsTest) 62 list(APPEND CLI11_TESTS WindowsTest)
56 endif() 63 endif()
57 64
  65 +if (Boost_FOUND)
  66 + list(APPEND CLI11_TESTS BoostOptionTypeTest)
  67 +endif()
  68 +
58 set(CLI11_MULTIONLY_TESTS TimerTest) 69 set(CLI11_MULTIONLY_TESTS TimerTest)
59 70
60 # Only affects current directory, so safe 71 # Only affects current directory, so safe
@@ -140,20 +151,29 @@ file(WRITE &quot;${PROJECT_BINARY_DIR}/CTestCustom.cmake&quot; @@ -140,20 +151,29 @@ file(WRITE &quot;${PROJECT_BINARY_DIR}/CTestCustom.cmake&quot;
140 "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")" 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 target_compile_definitions(informational PRIVATE ${boost-optional-def}) 154 target_compile_definitions(informational PRIVATE ${boost-optional-def})
149 target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def}) 155 target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def})
150 156
  157 +message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}")
  158 +
151 if(TARGET Boost::boost) 159 if(TARGET Boost::boost)
  160 + message(STATUS "including boost target")
152 target_link_libraries(informational PRIVATE Boost::boost) 161 target_link_libraries(informational PRIVATE Boost::boost)
153 target_link_libraries(OptionalTest PRIVATE Boost::boost) 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 elseif(BOOST_FOUND) 168 elseif(BOOST_FOUND)
  169 +message(STATUS "no boost target")
155 target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS}) 170 target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS})
156 target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS}) 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 endif() 177 endif()
158 178
159 if(CMAKE_BUILD_TYPE STREQUAL Coverage) 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,8 +6,10 @@
6 #include <cstdint> 6 #include <cstdint>
7 #include <cstdio> 7 #include <cstdio>
8 #include <fstream> 8 #include <fstream>
  9 +#include <map>
9 #include <string> 10 #include <string>
10 #include <tuple> 11 #include <tuple>
  12 +#include <unordered_map>
11 #include <utility> 13 #include <utility>
12 14
13 class NotStreamable {}; 15 class NotStreamable {};
@@ -52,6 +54,57 @@ TEST(TypeTools, type_size) { @@ -52,6 +54,57 @@ TEST(TypeTools, type_size) {
52 EXPECT_EQ(V, 5); 54 EXPECT_EQ(V, 5);
53 V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value; 55 V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value;
54 EXPECT_EQ(V, 2); 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 TEST(TypeTools, expected_count) { 110 TEST(TypeTools, expected_count) {
@@ -862,6 +915,10 @@ TEST(Types, TypeName) { @@ -862,6 +915,10 @@ TEST(Types, TypeName) {
862 CLI::detail::object_category::tuple_value, 915 CLI::detail::object_category::tuple_value,
863 "pair<int,string> does not read like a tuple"); 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 std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>(); 922 std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>();
866 EXPECT_EQ("[INT,TEXT]", pair_name); 923 EXPECT_EQ("[INT,TEXT]", pair_name);
867 924
@@ -869,7 +926,7 @@ TEST(Types, TypeName) { @@ -869,7 +926,7 @@ TEST(Types, TypeName) {
869 EXPECT_EQ("UINT", vector_name); 926 EXPECT_EQ("UINT", vector_name);
870 927
871 auto vclass = CLI::detail::classify_object<std::vector<std::vector<unsigned char>>>::value; 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 auto tclass = CLI::detail::classify_object<std::tuple<double>>::value; 931 auto tclass = CLI::detail::classify_object<std::tuple<double>>::value;
875 EXPECT_EQ(tclass, CLI::detail::object_category::number_constructible); 932 EXPECT_EQ(tclass, CLI::detail::object_category::number_constructible);
@@ -883,6 +940,18 @@ TEST(Types, TypeName) { @@ -883,6 +940,18 @@ TEST(Types, TypeName) {
883 tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>(); 940 tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>();
884 EXPECT_EQ("[INT,TEXT]", tuple_name); 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 tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>(); 955 tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>();
887 EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name); 956 EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name);
888 957
@@ -911,6 +980,8 @@ TEST(Types, TypeName) { @@ -911,6 +980,8 @@ TEST(Types, TypeName) {
911 "tuple<test> does not classify as a tuple"); 980 "tuple<test> does not classify as a tuple");
912 std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>(); 981 std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>();
913 EXPECT_EQ("ENUM", enum_name2); 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 TEST(Types, OverflowSmall) { 987 TEST(Types, OverflowSmall) {
@@ -995,11 +1066,11 @@ TEST(Types, LexicalCastParsable) { @@ -995,11 +1066,11 @@ TEST(Types, LexicalCastParsable) {
995 std::complex<double> output; 1066 std::complex<double> output;
996 EXPECT_TRUE(CLI::detail::lexical_cast(input, output)); 1067 EXPECT_TRUE(CLI::detail::lexical_cast(input, output));
997 EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble 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 EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output)); 1071 EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output));
1001 EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble 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 EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); 1075 EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
1005 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); 1076 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
@@ -1173,6 +1244,26 @@ TEST(Types, LexicalConversionComplex) { @@ -1173,6 +1244,26 @@ TEST(Types, LexicalConversionComplex) {
1173 EXPECT_EQ(x.imag(), 3.5); 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 TEST(FixNewLines, BasicCheck) { 1267 TEST(FixNewLines, BasicCheck) {
1177 std::string input = "one\ntwo"; 1268 std::string input = "one\ntwo";
1178 std::string output = "one\n; two"; 1269 std::string output = "one\n; two";
tests/NewParseTest.cpp
@@ -7,47 +7,36 @@ using ::testing::HasSubstr; @@ -7,47 +7,36 @@ using ::testing::HasSubstr;
7 7
8 using cx = std::complex<double>; 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 run(); 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 cx comp{1, 2}; 31 cx comp{1, 2};
45 - add_option(app, "-c,--complex", comp, "", true); 32 + app.add_option("-c,--complex", comp, "", true);
  33 +
46 args = {"-c", "4", "3"}; 34 args = {"-c", "4", "3"};
47 35
48 std::string help = app.help(); 36 std::string help = app.help();
49 EXPECT_THAT(help, HasSubstr("1")); 37 EXPECT_THAT(help, HasSubstr("1"));
50 EXPECT_THAT(help, HasSubstr("2")); 38 EXPECT_THAT(help, HasSubstr("2"));
  39 + EXPECT_THAT(help, HasSubstr("COMPLEX"));
51 40
52 EXPECT_DOUBLE_EQ(1, comp.real()); 41 EXPECT_DOUBLE_EQ(1, comp.real());
53 EXPECT_DOUBLE_EQ(2, comp.imag()); 42 EXPECT_DOUBLE_EQ(2, comp.imag());
@@ -58,9 +47,9 @@ TEST_F(TApp, DefaultComplex) { @@ -58,9 +47,9 @@ TEST_F(TApp, DefaultComplex) {
58 EXPECT_DOUBLE_EQ(3, comp.imag()); 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 args = {"-c", "4", "3"}; 54 args = {"-c", "4", "3"};
66 55
@@ -69,18 +58,18 @@ TEST_F(TApp, BuiltinComplex) { @@ -69,18 +58,18 @@ TEST_F(TApp, BuiltinComplex) {
69 EXPECT_THAT(help, HasSubstr("2")); 58 EXPECT_THAT(help, HasSubstr("2"));
70 EXPECT_THAT(help, HasSubstr("COMPLEX")); 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 run(); 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 std::complex<float> comp{1, 2}; 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 args = {"-c", "4", "3"}; 74 args = {"-c", "4", "3"};
86 75
@@ -98,7 +87,7 @@ TEST_F(TApp, BuiltinComplexFloat) { @@ -98,7 +87,7 @@ TEST_F(TApp, BuiltinComplexFloat) {
98 EXPECT_FLOAT_EQ(3, comp.imag()); 87 EXPECT_FLOAT_EQ(3, comp.imag());
99 } 88 }
100 89
101 -TEST_F(TApp, BuiltinComplexWithDelimiter) { 90 +TEST_F(TApp, ComplexWithDelimiter) {
102 cx comp{1, 2}; 91 cx comp{1, 2};
103 app.add_complex("-c,--complex", comp, "", true)->delimiter('+'); 92 app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
104 93
@@ -130,7 +119,39 @@ TEST_F(TApp, BuiltinComplexWithDelimiter) { @@ -130,7 +119,39 @@ TEST_F(TApp, BuiltinComplexWithDelimiter) {
130 EXPECT_DOUBLE_EQ(-4, comp.imag()); 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 cx comp{1, 2}; 155 cx comp{1, 2};
135 app.add_complex("-c,--complex", comp); 156 app.add_complex("-c,--complex", comp);
136 157
@@ -142,7 +163,19 @@ TEST_F(TApp, BuiltinComplexIgnoreI) { @@ -142,7 +163,19 @@ TEST_F(TApp, BuiltinComplexIgnoreI) {
142 EXPECT_DOUBLE_EQ(3, comp.imag()); 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 cx comp{1, 2}; 179 cx comp{1, 2};
147 app.add_complex("-c,--complex", comp); 180 app.add_complex("-c,--complex", comp);
148 181
@@ -176,7 +209,41 @@ TEST_F(TApp, BuiltinComplexSingleArg) { @@ -176,7 +209,41 @@ TEST_F(TApp, BuiltinComplexSingleArg) {
176 EXPECT_DOUBLE_EQ(-2.7, comp.imag()); 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 cx comp{1, 2}; 247 cx comp{1, 2};
181 app.add_complex("-c,--complex", comp); 248 app.add_complex("-c,--complex", comp);
182 249
@@ -199,6 +266,29 @@ TEST_F(TApp, BuiltinComplexSingleImag) { @@ -199,6 +266,29 @@ TEST_F(TApp, BuiltinComplexSingleImag) {
199 EXPECT_DOUBLE_EQ(0, comp.imag()); 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 /// Simple class containing two strings useful for testing lexical cast and conversions 292 /// Simple class containing two strings useful for testing lexical cast and conversions
203 class spair { 293 class spair {
204 public: 294 public:
@@ -245,98 +335,6 @@ TEST_F(TApp, custom_string_converterFail) { @@ -245,98 +335,6 @@ TEST_F(TApp, custom_string_converterFail) {
245 EXPECT_THROW(run(), CLI::ConversionError); 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 /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the 338 /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
341 /// option assignments 339 /// option assignments
342 template <class X> class objWrapper { 340 template <class X> class objWrapper {
@@ -523,3 +521,107 @@ TEST_F(TApp, uint16Wrapper) { @@ -523,3 +521,107 @@ TEST_F(TApp, uint16Wrapper) {
523 521
524 EXPECT_THROW(run(), CLI::ConversionError); 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 #include <cstdint> 2 #include <cstdint>
2 #include <cstdlib> 3 #include <cstdlib>
3 #include <iostream> 4 #include <iostream>
@@ -72,6 +73,44 @@ TEST_F(TApp, StdOptionalTest) { @@ -72,6 +73,44 @@ TEST_F(TApp, StdOptionalTest) {
72 EXPECT_EQ(*opt, 3); 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 #ifdef _MSC_VER 114 #ifdef _MSC_VER
76 #pragma warning(default : 4244) 115 #pragma warning(default : 4244)
77 #endif 116 #endif
@@ -165,11 +204,16 @@ TEST_F(TApp, BoostOptionalStringTest) { @@ -165,11 +204,16 @@ TEST_F(TApp, BoostOptionalStringTest) {
165 EXPECT_TRUE(opt); 204 EXPECT_TRUE(opt);
166 EXPECT_EQ(*opt, "strv"); 205 EXPECT_EQ(*opt, "strv");
167 } 206 }
  207 +namespace boost {
  208 +using CLI::enums::operator<<;
  209 +}
168 210
169 TEST_F(TApp, BoostOptionalEnumTest) { 211 TEST_F(TApp, BoostOptionalEnumTest) {
  212 +
170 enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 }; 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 auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt); 215 auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt);
  216 + app.add_option_no_stream("-e,--eval", opt2);
173 optptr->capture_default_str(); 217 optptr->capture_default_str();
174 218
175 auto dstring = optptr->get_default_str(); 219 auto dstring = optptr->get_default_str();
@@ -206,6 +250,7 @@ TEST_F(TApp, BoostOptionalVector) { @@ -206,6 +250,7 @@ TEST_F(TApp, BoostOptionalVector) {
206 TEST_F(TApp, BoostOptionalVectorEmpty) { 250 TEST_F(TApp, BoostOptionalVectorEmpty) {
207 boost::optional<std::vector<int>> opt; 251 boost::optional<std::vector<int>> opt;
208 app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); 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 run(); 254 run();
210 EXPECT_FALSE(opt); 255 EXPECT_FALSE(opt);
211 args = {"-v"}; 256 args = {"-v"};
@@ -219,6 +264,44 @@ TEST_F(TApp, BoostOptionalVectorEmpty) { @@ -219,6 +264,44 @@ TEST_F(TApp, BoostOptionalVectorEmpty) {
219 EXPECT_EQ(*opt, expV); 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 #endif 305 #endif
223 306
224 #if !CLI11_OPTIONAL 307 #if !CLI11_OPTIONAL
tests/TrueFalseTest.cpp
1 #include "app_helper.hpp" 1 #include "app_helper.hpp"
2 2
3 /// This allows a set of strings to be run over by a test 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 TEST_P(TApp_TBO, TrueBoolOption) { 6 TEST_P(TApp_TBO, TrueBoolOption) {
7 bool value{false}; // Not used, but set just in case 7 bool value{false}; // Not used, but set just in case
@@ -13,10 +13,10 @@ TEST_P(TApp_TBO, TrueBoolOption) { @@ -13,10 +13,10 @@ TEST_P(TApp_TBO, TrueBoolOption) {
13 } 13 }
14 14
15 // Change to INSTANTIATE_TEST_SUITE_P in GTest master 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 /// This allows a set of strings to be run over by a test 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 TEST_P(TApp_FBO, FalseBoolOptions) { 21 TEST_P(TApp_FBO, FalseBoolOptions) {
22 bool value{true}; // Not used, but set just in case 22 bool value{true}; // Not used, but set just in case
@@ -27,4 +27,4 @@ TEST_P(TApp_FBO, FalseBoolOptions) { @@ -27,4 +27,4 @@ TEST_P(TApp_FBO, FalseBoolOptions) {
27 EXPECT_FALSE(value); 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,10 +11,11 @@
11 11
12 using input_t = std::vector<std::string>; 12 using input_t = std::vector<std::string>;
13 13
14 -struct TApp : public ::testing::Test { 14 +class TApp_base {
  15 + public:
15 CLI::App app{"My Test Program"}; 16 CLI::App app{"My Test Program"};
16 input_t args{}; 17 input_t args{};
17 - 18 + virtual ~TApp_base() = default;
18 void run() { 19 void run() {
19 // It is okay to re-parse - clear is called automatically before a parse. 20 // It is okay to re-parse - clear is called automatically before a parse.
20 input_t newargs = args; 21 input_t newargs = args;
@@ -23,6 +24,8 @@ struct TApp : public ::testing::Test { @@ -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 class TempFile { 29 class TempFile {
27 std::string _name{}; 30 std::string _name{};
28 31