Commit 127f5388abc127d2784c1b5c00c2cf77a07a07ac

Authored by Philip Top
Committed by Henry Schreiner
1 parent 4bfce437

Support tuple (#307)

* add some tests with default capture on the two parameter template and some notes about it in the README.md

remove the test from visual studio 2015
vs2015 doesn't seem to properly deal with is_assignable in the cases we care about so make a standalone version that is more direct in what we are doing

add version to appveyor and add some notes to the readme

fix a few test cases to make sure code is covered and test a few other paths

remove unneeded enum streaming operator

add some diagnostic escapes around trait code to eliminate gcc Wnarrowing warnings

work specification of the template operations

remove optional add some templates for options conversions

add the two parameter template for add_option

* Fix some comments from Code review and add more description

* start work on trying to clean up the type traits for which lexical cast overload to use

* fix readme issue and make the condition tests a little clearer

* add a check for out of range errors on boolean conversions

* Fix capitalization and some comments on option functions

* Allow immediate_callback on the main app to run the main app callback prior to named subcommand callbacks, and reflect this change in the a new test and docs.

* add a is_tuple_like trait, and type_count structure for getting the number of elements to require

* add lexical conversion functions for tuples and other types

* remove the separate vector option and option function

* test out the type names for tuples

* add some more lexical conversion functions and test

* more work on tuples and tests

* fix some merge warnings

* fix some typename usage and c++14 only constructs

* tweak some of the template to remove undefined references

* add extra static assert about is_tuple_like

* fix some undefined references in clang

* move around some of the type_count templates to be used in the type_name functions

* move the type_count around and add some additional checks on the classification

* add some info to the readme
README.md
@@ -194,7 +194,7 @@ While all options internally are the same type, there are several ways to add an @@ -194,7 +194,7 @@ While all options internally are the same type, there are several ways to add an
194 app.add_option(option_name, help_str="") // πŸ†• 194 app.add_option(option_name, help_str="") // πŸ†•
195 195
196 app.add_option(option_name, 196 app.add_option(option_name,
197 - 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. 197 + 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(up to 5 elements) and tuple like structures such as std::array or std::pair.
198 help_string="") 198 help_string="")
199 199
200 app.add_option_function<type>(option_name, 200 app.add_option_function<type>(option_name,
@@ -202,7 +202,7 @@ app.add_option_function&lt;type&gt;(option_name, @@ -202,7 +202,7 @@ app.add_option_function&lt;type&gt;(option_name,
202 help_string="") 202 help_string="")
203 203
204 app.add_complex(... // Special case: support for complex numbers 204 app.add_complex(... // Special case: support for complex numbers
205 -//🚧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. 205 +//🚧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.
206 app.add_option<typename T, typename XC>(option_name, 206 app.add_option<typename T, typename XC>(option_name,
207 T &output, // output must be assignable or constructible from a value of type XC 207 T &output, // output must be assignable or constructible from a value of type XC
208 help_string="") 208 help_string="")
@@ -212,7 +212,7 @@ app.add_flag(option_name, @@ -212,7 +212,7 @@ app.add_flag(option_name,
212 help_string="") 212 help_string="")
213 213
214 app.add_flag(option_name, 214 app.add_flag(option_name,
215 - variable_to_bind_to, // bool, int, πŸ†• float, πŸ†• vector, πŸ†• enum, or πŸ†• string-like, or πŸ†• anything with a defined conversion from a string like add_option 215 + 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
216 help_string="") 216 help_string="")
217 217
218 app.add_flag_function(option_name, // πŸ†• 218 app.add_flag_function(option_name, // πŸ†•
@@ -249,7 +249,7 @@ The `add_option_function&lt;type&gt;(...` function will typically require the template @@ -249,7 +249,7 @@ The `add_option_function&lt;type&gt;(...` function will typically require the template
249 double val 249 double val
250 app.add_option<double,unsigned int>("-v",val); 250 app.add_option<double,unsigned int>("-v",val);
251 ``` 251 ```
252 -which would first verify the input is convertible to an int before assigning it. Or using some variant type 252 +which would first verify the input is convertible to an unsigned int before assigning it. Or using some variant type
253 ``` 253 ```
254 using vtype=std::variant<int, double, std::string>; 254 using vtype=std::variant<int, double, std::string>;
255 vtype v1; 255 vtype v1;
@@ -261,6 +261,13 @@ otherwise the output would default to a string. The add_option can be used with @@ -261,6 +261,13 @@ otherwise the output would default to a string. The add_option can be used with
261 261
262 Type such as optional<int>, optional<double>, and optional<string> are supported directly, other optional types can be added using the two parameter template. See [CLI11 Internals][] for information on how this could done and how you can add your own converters for additional types. 262 Type such as optional<int>, optional<double>, and optional<string> are supported directly, other optional types can be added using the two parameter template. See [CLI11 Internals][] for information on how this could done and how you can add your own converters for additional types.
263 263
  264 +Vector types can also be used int the two parameter template overload
  265 +```
  266 +std::vector<double> v1;
  267 +app.add_option<std::vector<double>,int>("--vs",v1);
  268 +```
  269 +would load a vector of doubles but ensure all values can be represented as integers.
  270 +
264 Automatic direct capture of the default string is disabled when using the two parameter template. Use `set_default_str(...)` or `->default_function(std::string())` to set the default string or capture function directly for these cases. 271 Automatic direct capture of the default string is disabled when using the two parameter template. Use `set_default_str(...)` or `->default_function(std::string())` to set the default string or capture function directly for these cases.
265 272
266 πŸ†• Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example: 273 πŸ†• Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example:
include/CLI/App.hpp
@@ -468,35 +468,37 @@ class App { @@ -468,35 +468,37 @@ class App {
468 468
469 /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) 469 /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
470 470
471 - template <typename T,  
472 - typename XC = T,  
473 - enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy> 471 + template <typename T, typename XC = T, enable_if_t<!std::is_const<XC>::value, detail::enabler> = detail::dummy>
474 Option *add_option(std::string option_name, 472 Option *add_option(std::string option_name,
475 T &variable, ///< The variable to set 473 T &variable, ///< The variable to set
476 std::string option_description = "", 474 std::string option_description = "",
477 bool defaulted = false) { 475 bool defaulted = false) {
478 476
479 auto fun = [&variable](CLI::results_t res) { // comment for spacing 477 auto fun = [&variable](CLI::results_t res) { // comment for spacing
480 - return detail::lexical_assign<T, XC>(res[0], variable); 478 + return detail::lexical_conversion<T, XC>(res, variable);
481 }; 479 };
482 480
483 Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { 481 Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
484 - return std::string(CLI::detail::checked_to_string<T, XC>(variable)); 482 + return CLI::detail::checked_to_string<T, XC>(variable);
485 }); 483 });
486 opt->type_name(detail::type_name<XC>()); 484 opt->type_name(detail::type_name<XC>());
487 - 485 + // these must be actual variable since (std::max) sometimes is defined in terms of references and references to
  486 + // structs used in the evaluation can be temporary so that would cause issues.
  487 + auto Tcount = detail::type_count<T>::value;
  488 + auto XCcount = detail::type_count<XC>::value;
  489 + opt->type_size((std::max)(Tcount, XCcount));
488 return opt; 490 return opt;
489 } 491 }
490 492
491 /// Add option for a callback of a specific type 493 /// Add option for a callback of a specific type
492 - template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> 494 + template <typename T>
493 Option *add_option_function(std::string option_name, 495 Option *add_option_function(std::string option_name,
494 const std::function<void(const T &)> &func, ///< the callback to execute 496 const std::function<void(const T &)> &func, ///< the callback to execute
495 std::string option_description = "") { 497 std::string option_description = "") {
496 498
497 auto fun = [func](CLI::results_t res) { 499 auto fun = [func](CLI::results_t res) {
498 T variable; 500 T variable;
499 - bool result = detail::lexical_cast(res[0], variable); 501 + bool result = detail::lexical_conversion<T, T>(res, variable);
500 if(result) { 502 if(result) {
501 func(variable); 503 func(variable);
502 } 504 }
@@ -505,6 +507,7 @@ class App { @@ -505,6 +507,7 @@ class App {
505 507
506 Option *opt = add_option(option_name, std::move(fun), option_description, false); 508 Option *opt = add_option(option_name, std::move(fun), option_description, false);
507 opt->type_name(detail::type_name<T>()); 509 opt->type_name(detail::type_name<T>());
  510 + opt->type_size(detail::type_count<T>::value);
508 return opt; 511 return opt;
509 } 512 }
510 513
@@ -521,65 +524,6 @@ class App { @@ -521,65 +524,6 @@ class App {
521 return add_option(option_name, CLI::callback_t(), option_description, false); 524 return add_option(option_name, CLI::callback_t(), option_description, false);
522 } 525 }
523 526
524 - /// Add option for vectors  
525 - template <typename T>  
526 - Option *add_option(std::string option_name,  
527 - std::vector<T> &variable, ///< The variable vector to set  
528 - std::string option_description = "",  
529 - bool defaulted = false) {  
530 -  
531 - auto fun = [&variable](CLI::results_t res) {  
532 - bool retval = true;  
533 - variable.clear();  
534 - variable.reserve(res.size());  
535 - for(const auto &elem : res) {  
536 -  
537 - variable.emplace_back();  
538 - retval &= detail::lexical_cast(elem, variable.back());  
539 - }  
540 - return (!variable.empty()) && retval;  
541 - };  
542 -  
543 - auto default_function = [&variable]() {  
544 - std::vector<std::string> defaults;  
545 - defaults.resize(variable.size());  
546 - std::transform(variable.begin(), variable.end(), defaults.begin(), [](T &val) {  
547 - return std::string(CLI::detail::to_string(val));  
548 - });  
549 - return std::string("[" + detail::join(defaults) + "]");  
550 - };  
551 -  
552 - Option *opt = add_option(option_name, fun, option_description, defaulted, default_function);  
553 - opt->type_name(detail::type_name<T>())->type_size(-1);  
554 -  
555 - return opt;  
556 - }  
557 -  
558 - /// Add option for a vector callback of a specific type  
559 - template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>  
560 - Option *add_option_function(std::string option_name,  
561 - const std::function<void(const T &)> &func, ///< the callback to execute  
562 - std::string option_description = "") {  
563 -  
564 - CLI::callback_t fun = [func](CLI::results_t res) {  
565 - T values;  
566 - bool retval = true;  
567 - values.reserve(res.size());  
568 - for(const auto &elem : res) {  
569 - values.emplace_back();  
570 - retval &= detail::lexical_cast(elem, values.back());  
571 - }  
572 - if(retval) {  
573 - func(values);  
574 - }  
575 - return retval;  
576 - };  
577 -  
578 - Option *opt = add_option(option_name, std::move(fun), std::move(option_description), false);  
579 - opt->type_name(detail::type_name<T>())->type_size(-1);  
580 - return opt;  
581 - }  
582 -  
583 /// Set a help flag, replace the existing one if present 527 /// Set a help flag, replace the existing one if present
584 Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") { 528 Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
585 // take flag_description by const reference otherwise add_flag tries to assign to help_description 529 // take flag_description by const reference otherwise add_flag tries to assign to help_description
include/CLI/TypeTools.hpp
@@ -142,7 +142,7 @@ template &lt;typename T, typename C&gt; class is_direct_constructible { @@ -142,7 +142,7 @@ template &lt;typename T, typename C&gt; class is_direct_constructible {
142 template <typename, typename> static auto test(...) -> std::false_type; 142 template <typename, typename> static auto test(...) -> std::false_type;
143 143
144 public: 144 public:
145 - static const bool value = decltype(test<T, C>(0))::value; 145 + static constexpr bool value = decltype(test<T, C>(0))::value;
146 }; 146 };
147 #ifdef __GNUC__ 147 #ifdef __GNUC__
148 #pragma GCC diagnostic pop 148 #pragma GCC diagnostic pop
@@ -158,7 +158,7 @@ template &lt;typename T, typename S = std::ostringstream&gt; class is_ostreamable { @@ -158,7 +158,7 @@ template &lt;typename T, typename S = std::ostringstream&gt; class is_ostreamable {
158 template <typename, typename> static auto test(...) -> std::false_type; 158 template <typename, typename> static auto test(...) -> std::false_type;
159 159
160 public: 160 public:
161 - static const bool value = decltype(test<T, S>(0))::value; 161 + static constexpr bool value = decltype(test<T, S>(0))::value;
162 }; 162 };
163 163
164 /// Check for input streamability 164 /// Check for input streamability
@@ -169,7 +169,7 @@ template &lt;typename T, typename S = std::istringstream&gt; class is_istreamable { @@ -169,7 +169,7 @@ template &lt;typename T, typename S = std::istringstream&gt; class is_istreamable {
169 template <typename, typename> static auto test(...) -> std::false_type; 169 template <typename, typename> static auto test(...) -> std::false_type;
170 170
171 public: 171 public:
172 - static const bool value = decltype(test<T, S>(0))::value; 172 + static constexpr bool value = decltype(test<T, S>(0))::value;
173 }; 173 };
174 174
175 /// Templated operation to get a value from a stream 175 /// Templated operation to get a value from a stream
@@ -186,6 +186,18 @@ bool from_stream(const std::string &amp; /*istring*/, T &amp; /*obj*/) { @@ -186,6 +186,18 @@ bool from_stream(const std::string &amp; /*istring*/, T &amp; /*obj*/) {
186 return false; 186 return false;
187 } 187 }
188 188
  189 +// Check for tuple like types, as in classes with a tuple_size type trait
  190 +template <typename S> class is_tuple_like {
  191 + template <typename SS>
  192 + // static auto test(int)
  193 + // -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
  194 + static auto test(int) -> decltype(std::tuple_size<SS>::value, std::true_type{});
  195 + template <typename> static auto test(...) -> std::false_type;
  196 +
  197 + public:
  198 + static constexpr bool value = decltype(test<S>(0))::value;
  199 +};
  200 +
189 /// Convert an object to a string (directly forward if this can become a string) 201 /// Convert an object to a string (directly forward if this can become a string)
190 template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy> 202 template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
191 auto to_string(T &&value) -> decltype(std::forward<T>(value)) { 203 auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
@@ -204,12 +216,30 @@ std::string to_string(T &amp;&amp;value) { @@ -204,12 +216,30 @@ std::string to_string(T &amp;&amp;value) {
204 216
205 /// If conversion is not supported, return an empty string (streaming is not supported for that type) 217 /// If conversion is not supported, return an empty string (streaming is not supported for that type)
206 template <typename T, 218 template <typename T,
207 - enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value, detail::enabler> =  
208 - detail::dummy> 219 + enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
  220 + !is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
  221 + detail::enabler> = detail::dummy>
209 std::string to_string(T &&) { 222 std::string to_string(T &&) {
210 return std::string{}; 223 return std::string{};
211 } 224 }
212 225
  226 +/// convert a vector to a string
  227 +template <typename T,
  228 + enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
  229 + is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
  230 + detail::enabler> = detail::dummy>
  231 +std::string to_string(T &&variable) {
  232 + std::vector<std::string> defaults;
  233 + defaults.reserve(variable.size());
  234 + auto cval = variable.begin();
  235 + auto end = variable.end();
  236 + while(cval != end) {
  237 + defaults.emplace_back(CLI::detail::to_string(*cval));
  238 + ++cval;
  239 + }
  240 + return std::string("[" + detail::join(defaults) + "]");
  241 +}
  242 +
213 /// special template overload 243 /// special template overload
214 template <typename T1, 244 template <typename T1,
215 typename T2, 245 typename T2,
@@ -228,6 +258,25 @@ std::string checked_to_string(T &amp;&amp;) { @@ -228,6 +258,25 @@ std::string checked_to_string(T &amp;&amp;) {
228 return std::string{}; 258 return std::string{};
229 } 259 }
230 260
  261 +/// This will only trigger for actual void type
  262 +template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
  263 +
  264 +/// Set of overloads to get the type size of an object
  265 +template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
  266 + static constexpr int value{std::tuple_size<T>::value};
  267 +};
  268 +/// Type size for regular object types that do not look like a tuple
  269 +template <typename T>
  270 +struct type_count<
  271 + T,
  272 + typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> {
  273 + static constexpr int value{1};
  274 +};
  275 +/// Type size of types that look like a vector
  276 +template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> {
  277 + static constexpr int value{-1};
  278 +};
  279 +
231 // Enumeration of the different supported categorizations of objects 280 // Enumeration of the different supported categorizations of objects
232 enum objCategory : int { 281 enum objCategory : int {
233 integral_value = 2, 282 integral_value = 2,
@@ -239,6 +288,7 @@ enum objCategory : int { @@ -239,6 +288,7 @@ enum objCategory : int {
239 double_constructible = 14, 288 double_constructible = 14,
240 integer_constructible = 16, 289 integer_constructible = 16,
241 vector_value = 30, 290 vector_value = 30,
  291 + tuple_value = 35,
242 // string assignable or greater used in a condition so anything string like must come last 292 // string assignable or greater used in a condition so anything string like must come last
243 string_assignable = 50, 293 string_assignable = 50,
244 string_constructible = 60, 294 string_constructible = 60,
@@ -308,36 +358,49 @@ template &lt;typename T&gt; struct uncommon_type { @@ -308,36 +358,49 @@ template &lt;typename T&gt; struct uncommon_type {
308 !std::is_enum<T>::value, 358 !std::is_enum<T>::value,
309 std::true_type, 359 std::true_type,
310 std::false_type>::type; 360 std::false_type>::type;
311 - static const bool value = type::value; 361 + static constexpr bool value = type::value;
312 }; 362 };
313 363
314 /// Assignable from double or int 364 /// Assignable from double or int
315 template <typename T> 365 template <typename T>
316 -struct classify_object<T,  
317 - typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&  
318 - is_direct_constructible<T, int>::value>::type> { 366 +struct classify_object<
  367 + T,
  368 + typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
  369 + is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
319 static constexpr objCategory value{number_constructible}; 370 static constexpr objCategory value{number_constructible};
320 }; 371 };
321 372
322 /// Assignable from int 373 /// Assignable from int
323 template <typename T> 374 template <typename T>
324 -struct classify_object<T,  
325 - typename std::enable_if<uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&  
326 - is_direct_constructible<T, int>::value>::type> {  
327 - static const objCategory value{integer_constructible}; 375 +struct classify_object<
  376 + T,
  377 + typename std::enable_if<uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
  378 + is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
  379 + static constexpr objCategory value{integer_constructible};
328 }; 380 };
329 381
330 /// Assignable from double 382 /// Assignable from double
331 template <typename T> 383 template <typename T>
332 -struct classify_object<T,  
333 - typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&  
334 - !is_direct_constructible<T, int>::value>::type> {  
335 - static const objCategory value{double_constructible}; 384 +struct classify_object<
  385 + T,
  386 + typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
  387 + !is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
  388 + static constexpr objCategory value{double_constructible};
336 }; 389 };
337 390
338 -/// vector type 391 +/// Tuple type
  392 +template <typename T>
  393 +struct classify_object<
  394 + T,
  395 + typename std::enable_if<(is_tuple_like<T>::value && uncommon_type<T>::value &&
  396 + !is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value) ||
  397 + type_count<T>::value >= 2>::type> {
  398 + static constexpr objCategory value{tuple_value};
  399 +};
  400 +
  401 +/// Vector type
339 template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> { 402 template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> {
340 - static const objCategory value{vector_value}; 403 + static constexpr objCategory value{vector_value};
341 }; 404 };
342 405
343 // Type name print 406 // Type name print
@@ -367,11 +430,6 @@ constexpr const char *type_name() { @@ -367,11 +430,6 @@ constexpr const char *type_name() {
367 return "FLOAT"; 430 return "FLOAT";
368 } 431 }
369 432
370 -/// This one should not be used, since vector types print the internal type  
371 -template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>  
372 -constexpr const char *type_name() {  
373 - return "VECTOR";  
374 -}  
375 /// Print name for enumeration types 433 /// Print name for enumeration types
376 template <typename T, enable_if_t<classify_object<T>::value == enumeration, detail::enabler> = detail::dummy> 434 template <typename T, enable_if_t<classify_object<T>::value == enumeration, detail::enabler> = detail::dummy>
377 constexpr const char *type_name() { 435 constexpr const char *type_name() {
@@ -390,6 +448,60 @@ constexpr const char *type_name() { @@ -390,6 +448,60 @@ constexpr const char *type_name() {
390 return "TEXT"; 448 return "TEXT";
391 } 449 }
392 450
  451 +/// This one should not be used normally, since vector types print the internal type
  452 +template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>
  453 +constexpr const char *type_name() {
  454 + return type_name<typename T::value_type>();
  455 +}
  456 +
  457 +/// Print name for tuple types
  458 +template <
  459 + typename T,
  460 + enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 1, detail::enabler> = detail::dummy>
  461 +std::string type_name() {
  462 + return type_name<typename std::tuple_element<0, T>::type>();
  463 +}
  464 +/// Print type name for 2 element tuples
  465 +template <
  466 + typename T,
  467 + enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 2, detail::enabler> = detail::dummy>
  468 +std::string type_name() {
  469 + return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
  470 + type_name<typename std::tuple_element<1, T>::type>() + "]";
  471 +}
  472 +
  473 +/// Print type name for 3 element tuples
  474 +template <
  475 + typename T,
  476 + enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 3, detail::enabler> = detail::dummy>
  477 +std::string type_name() {
  478 + return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
  479 + type_name<typename std::tuple_element<1, T>::type>() + "," +
  480 + type_name<typename std::tuple_element<2, T>::type>() + "]";
  481 +}
  482 +
  483 +/// Print type name for 4 element tuples
  484 +template <
  485 + typename T,
  486 + enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 4, detail::enabler> = detail::dummy>
  487 +std::string type_name() {
  488 + return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
  489 + type_name<typename std::tuple_element<1, T>::type>() + "," +
  490 + type_name<typename std::tuple_element<2, T>::type>() + "," +
  491 + type_name<typename std::tuple_element<3, T>::type>() + "]";
  492 +}
  493 +
  494 +/// Print type name for 5 element tuples
  495 +template <
  496 + typename T,
  497 + enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 5, detail::enabler> = detail::dummy>
  498 +std::string type_name() {
  499 + return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
  500 + type_name<typename std::tuple_element<1, T>::type>() + "," +
  501 + type_name<typename std::tuple_element<2, T>::type>() + "," +
  502 + type_name<typename std::tuple_element<3, T>::type>() + "," +
  503 + type_name<typename std::tuple_element<4, T>::type>() + "]";
  504 +}
393 // Lexical cast 505 // Lexical cast
394 506
395 /// Convert a flag into an integer value typically binary flags 507 /// Convert a flag into an integer value typically binary flags
@@ -574,19 +686,19 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) { @@ -574,19 +686,19 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
574 } 686 }
575 687
576 /// Assign a value through lexical cast operations 688 /// Assign a value through lexical cast operations
577 -template <class T, class XC, enable_if_t<std::is_same<T, XC>::value, detail::enabler> = detail::dummy> 689 +template <typename T, typename XC, enable_if_t<std::is_same<T, XC>::value, detail::enabler> = detail::dummy>
578 bool lexical_assign(const std::string &input, T &output) { 690 bool lexical_assign(const std::string &input, T &output) {
579 return lexical_cast(input, output); 691 return lexical_cast(input, output);
580 } 692 }
581 693
582 /// Assign a value converted from a string in lexical cast to the output value directly 694 /// Assign a value converted from a string in lexical cast to the output value directly
583 template < 695 template <
584 - class T,  
585 - class XC, 696 + typename T,
  697 + typename XC,
586 enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy> 698 enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
587 bool lexical_assign(const std::string &input, T &output) { 699 bool lexical_assign(const std::string &input, T &output) {
588 XC val; 700 XC val;
589 - auto parse_result = lexical_cast<XC>(input, val); 701 + bool parse_result = lexical_cast<XC>(input, val);
590 if(parse_result) { 702 if(parse_result) {
591 output = val; 703 output = val;
592 } 704 }
@@ -594,8 +706,8 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) { @@ -594,8 +706,8 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) {
594 } 706 }
595 707
596 /// Assign a value from a lexical cast through constructing a value and move assigning it 708 /// Assign a value from a lexical cast through constructing a value and move assigning it
597 -template <class T,  
598 - class XC, 709 +template <typename T,
  710 + typename XC,
599 enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value && 711 enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value &&
600 std::is_move_assignable<T>::value, 712 std::is_move_assignable<T>::value,
601 detail::enabler> = detail::dummy> 713 detail::enabler> = detail::dummy>
@@ -607,6 +719,160 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) { @@ -607,6 +719,160 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) {
607 } 719 }
608 return parse_result; 720 return parse_result;
609 } 721 }
  722 +/// Lexical conversion if there is only one element
  723 +template <typename T,
  724 + typename XC,
  725 + enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value, detail::enabler> =
  726 + detail::dummy>
  727 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  728 + return lexical_assign<T, XC>(strings[0], output);
  729 +}
  730 +
  731 +/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor
  732 +template <typename T,
  733 + typename XC,
  734 + enable_if_t<type_count<T>::value == 1 && type_count<XC>::value == 2, detail::enabler> = detail::dummy>
  735 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  736 + typename std::tuple_element<0, XC>::type v1;
  737 + typename std::tuple_element<1, XC>::type v2;
  738 + bool retval = lexical_cast(strings[0], v1);
  739 + if(strings.size() > 1) {
  740 + retval &= lexical_cast(strings[1], v2);
  741 + }
  742 + if(retval) {
  743 + output = T{v1, v2};
  744 + }
  745 + return retval;
  746 +}
  747 +
  748 +/// Lexical conversion of a vector types
  749 +template <class T,
  750 + class XC,
  751 + enable_if_t<type_count<T>::value == -1 && type_count<XC>::value == -1, detail::enabler> = detail::dummy>
  752 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  753 + bool retval = true;
  754 + output.clear();
  755 + output.reserve(strings.size());
  756 + for(const auto &elem : strings) {
  757 +
  758 + output.emplace_back();
  759 + retval &= lexical_assign<typename T::value_type, typename XC::value_type>(elem, output.back());
  760 + }
  761 + return (!output.empty()) && retval;
  762 +}
  763 +
  764 +/// Conversion to a vector type using a particular single type as the conversion type
  765 +template <class T,
  766 + class XC,
  767 + enable_if_t<(type_count<T>::value == -1) && (type_count<XC>::value == 1), detail::enabler> = detail::dummy>
  768 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  769 + bool retval = true;
  770 + output.clear();
  771 + output.reserve(strings.size());
  772 + for(const auto &elem : strings) {
  773 +
  774 + output.emplace_back();
  775 + retval &= lexical_assign<typename T::value_type, XC>(elem, output.back());
  776 + }
  777 + return (!output.empty()) && retval;
  778 +}
  779 +
  780 +/// Conversion for single element tuple and single element tuple conversion type
  781 +template <class T,
  782 + class XC,
  783 + enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && is_tuple_like<XC>::value,
  784 + detail::enabler> = detail::dummy>
  785 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  786 + static_assert(type_count<T>::value == type_count<XC>::value,
  787 + "when using converting to tuples different cross conversion are not possible");
  788 +
  789 + bool retval = lexical_assign<typename std::tuple_element<0, T>::type, typename std::tuple_element<0, XC>::type>(
  790 + strings[0], std::get<0>(output));
  791 + return retval;
  792 +}
  793 +
  794 +/// Conversion for single element tuple and single defined type
  795 +template <class T,
  796 + class XC,
  797 + enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && !is_tuple_like<XC>::value,
  798 + detail::enabler> = detail::dummy>
  799 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  800 + static_assert(type_count<T>::value == type_count<XC>::value,
  801 + "when using converting to tuples different cross conversion are not possible");
  802 +
  803 + bool retval = lexical_assign<typename std::tuple_element<0, T>::type, XC>(strings[0], std::get<0>(output));
  804 + return retval;
  805 +}
  806 +
  807 +/// conversion for two element tuple
  808 +template <class T, class XC, enable_if_t<type_count<T>::value == 2, detail::enabler> = detail::dummy>
  809 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  810 + static_assert(type_count<T>::value == type_count<XC>::value,
  811 + "when using converting to tuples different cross conversion are not possible");
  812 +
  813 + bool retval = lexical_cast(strings[0], std::get<0>(output));
  814 + if(strings.size() > 1) {
  815 + retval &= lexical_cast(strings[1], std::get<1>(output));
  816 + }
  817 + return retval;
  818 +}
  819 +
  820 +/// conversion for three element tuple
  821 +template <class T, class XC, enable_if_t<type_count<T>::value == 3, detail::enabler> = detail::dummy>
  822 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  823 + static_assert(type_count<T>::value == type_count<XC>::value,
  824 + "when using converting to tuples different cross conversion are not possible");
  825 +
  826 + bool retval = lexical_cast(strings[0], std::get<0>(output));
  827 + if(strings.size() > 1) {
  828 + retval &= lexical_cast(strings[1], std::get<1>(output));
  829 + }
  830 + if(strings.size() > 2) {
  831 + retval &= lexical_cast(strings[2], std::get<2>(output));
  832 + }
  833 + return retval;
  834 +}
  835 +
  836 +/// conversion for four element tuple
  837 +template <class T, class XC, enable_if_t<type_count<T>::value == 4, detail::enabler> = detail::dummy>
  838 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  839 + static_assert(type_count<T>::value == type_count<XC>::value,
  840 + "when using converting to tuples different cross conversion are not possible");
  841 +
  842 + bool retval = lexical_cast(strings[0], std::get<0>(output));
  843 + if(strings.size() > 1) {
  844 + retval &= lexical_cast(strings[1], std::get<1>(output));
  845 + }
  846 + if(strings.size() > 2) {
  847 + retval &= lexical_cast(strings[2], std::get<2>(output));
  848 + }
  849 + if(strings.size() > 3) {
  850 + retval &= lexical_cast(strings[3], std::get<3>(output));
  851 + }
  852 + return retval;
  853 +}
  854 +
  855 +/// conversion for five element tuple
  856 +template <class T, class XC, enable_if_t<type_count<T>::value == 5, detail::enabler> = detail::dummy>
  857 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  858 + static_assert(type_count<T>::value == type_count<XC>::value,
  859 + "when using converting to tuples different cross conversion are not possible");
  860 +
  861 + bool retval = lexical_cast(strings[0], std::get<0>(output));
  862 + if(strings.size() > 1) {
  863 + retval &= lexical_cast(strings[1], std::get<1>(output));
  864 + }
  865 + if(strings.size() > 2) {
  866 + retval &= lexical_cast(strings[2], std::get<2>(output));
  867 + }
  868 + if(strings.size() > 3) {
  869 + retval &= lexical_cast(strings[3], std::get<3>(output));
  870 + }
  871 + if(strings.size() > 4) {
  872 + retval &= lexical_cast(strings[4], std::get<4>(output));
  873 + }
  874 + return retval;
  875 +}
610 876
611 /// Sum a vector of flag representations 877 /// Sum a vector of flag representations
612 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is 878 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
tests/HelpersTest.cpp
1 #include "app_helper.hpp" 1 #include "app_helper.hpp"
2 2
  3 +#include <array>
3 #include <climits> 4 #include <climits>
4 #include <complex> 5 #include <complex>
5 #include <cstdint> 6 #include <cstdint>
6 #include <cstdio> 7 #include <cstdio>
7 #include <fstream> 8 #include <fstream>
8 #include <string> 9 #include <string>
  10 +#include <tuple>
9 11
10 class NotStreamable {}; 12 class NotStreamable {};
11 13
@@ -25,6 +27,30 @@ TEST(TypeTools, Streaming) { @@ -25,6 +27,30 @@ TEST(TypeTools, Streaming) {
25 EXPECT_EQ(CLI::detail::to_string(std::string("string")), std::string("string")); 27 EXPECT_EQ(CLI::detail::to_string(std::string("string")), std::string("string"));
26 } 28 }
27 29
  30 +TEST(TypeTools, tuple) {
  31 + EXPECT_FALSE(CLI::detail::is_tuple_like<int>::value);
  32 + EXPECT_FALSE(CLI::detail::is_tuple_like<std::vector<double>>::value);
  33 + auto v = CLI::detail::is_tuple_like<std::tuple<double, int>>::value;
  34 + EXPECT_TRUE(v);
  35 + v = CLI::detail::is_tuple_like<std::tuple<double, double, double>>::value;
  36 + EXPECT_TRUE(v);
  37 +}
  38 +
  39 +TEST(TypeTools, type_size) {
  40 + auto V = CLI::detail::type_count<int>::value;
  41 + EXPECT_EQ(V, 1);
  42 + V = CLI::detail::type_count<void>::value;
  43 + EXPECT_EQ(V, 0);
  44 + V = CLI::detail::type_count<std::vector<double>>::value;
  45 + EXPECT_EQ(V, -1);
  46 + V = CLI::detail::type_count<std::tuple<double, int>>::value;
  47 + EXPECT_EQ(V, 2);
  48 + V = CLI::detail::type_count<std::tuple<std::string, double, int>>::value;
  49 + EXPECT_EQ(V, 3);
  50 + V = CLI::detail::type_count<std::array<std::string, 5>>::value;
  51 + EXPECT_EQ(V, 5);
  52 +}
  53 +
28 TEST(Split, SimpleByToken) { 54 TEST(Split, SimpleByToken) {
29 auto out = CLI::detail::split("one.two.three", '.'); 55 auto out = CLI::detail::split("one.two.three", '.');
30 ASSERT_EQ(3u, out.size()); 56 ASSERT_EQ(3u, out.size());
@@ -782,7 +808,33 @@ TEST(Types, TypeName) { @@ -782,7 +808,33 @@ TEST(Types, TypeName) {
782 EXPECT_EQ("FLOAT", float_name); 808 EXPECT_EQ("FLOAT", float_name);
783 809
784 std::string vector_name = CLI::detail::type_name<std::vector<int>>(); 810 std::string vector_name = CLI::detail::type_name<std::vector<int>>();
785 - EXPECT_EQ("VECTOR", vector_name); 811 + EXPECT_EQ("INT", vector_name);
  812 +
  813 + vector_name = CLI::detail::type_name<std::vector<double>>();
  814 + EXPECT_EQ("FLOAT", vector_name);
  815 +
  816 + vector_name = CLI::detail::type_name<std::vector<std::vector<unsigned char>>>();
  817 + EXPECT_EQ("UINT", vector_name);
  818 + auto vclass = CLI::detail::classify_object<std::tuple<double>>::value;
  819 + EXPECT_EQ(vclass, CLI::detail::objCategory::number_constructible);
  820 +
  821 + std::string tuple_name = CLI::detail::type_name<std::tuple<double>>();
  822 + EXPECT_EQ("FLOAT", tuple_name);
  823 +
  824 + static_assert(CLI::detail::classify_object<std::tuple<int, std::string>>::value ==
  825 + CLI::detail::objCategory::tuple_value,
  826 + "tuple<int,string> does not read like a tuple");
  827 + tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>();
  828 + EXPECT_EQ("[INT,TEXT]", tuple_name);
  829 +
  830 + tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>();
  831 + EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name);
  832 +
  833 + tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int>>();
  834 + EXPECT_EQ("[INT,TEXT,FLOAT,UINT]", tuple_name);
  835 +
  836 + tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int, std::string>>();
  837 + EXPECT_EQ("[INT,TEXT,FLOAT,UINT,TEXT]", tuple_name);
786 838
787 std::string text_name = CLI::detail::type_name<std::string>(); 839 std::string text_name = CLI::detail::type_name<std::string>();
788 EXPECT_EQ("TEXT", text_name); 840 EXPECT_EQ("TEXT", text_name);
@@ -793,6 +845,13 @@ TEST(Types, TypeName) { @@ -793,6 +845,13 @@ TEST(Types, TypeName) {
793 enum class test { test1, test2, test3 }; 845 enum class test { test1, test2, test3 };
794 std::string enum_name = CLI::detail::type_name<test>(); 846 std::string enum_name = CLI::detail::type_name<test>();
795 EXPECT_EQ("ENUM", enum_name); 847 EXPECT_EQ("ENUM", enum_name);
  848 +
  849 + vclass = CLI::detail::classify_object<std::tuple<test>>::value;
  850 + EXPECT_EQ(vclass, CLI::detail::objCategory::tuple_value);
  851 + static_assert(CLI::detail::classify_object<std::tuple<test>>::value == CLI::detail::objCategory::tuple_value,
  852 + "tuple<test> does not classify as a tuple");
  853 + std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>();
  854 + EXPECT_EQ("ENUM", enum_name2);
796 } 855 }
797 856
798 TEST(Types, OverflowSmall) { 857 TEST(Types, OverflowSmall) {
@@ -906,6 +965,121 @@ TEST(Types, LexicalCastEnum) { @@ -906,6 +965,121 @@ TEST(Types, LexicalCastEnum) {
906 EXPECT_EQ(output2, t2::enum3); 965 EXPECT_EQ(output2, t2::enum3);
907 } 966 }
908 967
  968 +TEST(Types, LexicalConversionDouble) {
  969 + CLI::results_t input = {"9.12"};
  970 + long double x;
  971 + bool res = CLI::detail::lexical_conversion<long double, double>(input, x);
  972 + EXPECT_TRUE(res);
  973 + EXPECT_FLOAT_EQ((float)9.12, (float)x);
  974 +
  975 + CLI::results_t bad_input = {"hello"};
  976 + res = CLI::detail::lexical_conversion<long double, double>(input, x);
  977 + EXPECT_TRUE(res);
  978 +}
  979 +
  980 +TEST(Types, LexicalConversionDoubleTuple) {
  981 + CLI::results_t input = {"9.12"};
  982 + std::tuple<double> x;
  983 + bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  984 + EXPECT_TRUE(res);
  985 + EXPECT_DOUBLE_EQ(9.12, std::get<0>(x));
  986 +
  987 + CLI::results_t bad_input = {"hello"};
  988 + res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  989 + EXPECT_TRUE(res);
  990 +}
  991 +
  992 +TEST(Types, LexicalConversionVectorDouble) {
  993 + CLI::results_t input = {"9.12", "10.79", "-3.54"};
  994 + std::vector<double> x;
  995 + bool res = CLI::detail::lexical_conversion<std::vector<double>, double>(input, x);
  996 + EXPECT_TRUE(res);
  997 + EXPECT_EQ(x.size(), 3u);
  998 + EXPECT_DOUBLE_EQ(x[2], -3.54);
  999 +
  1000 + res = CLI::detail::lexical_conversion<std::vector<double>, std::vector<double>>(input, x);
  1001 + EXPECT_TRUE(res);
  1002 + EXPECT_EQ(x.size(), 3u);
  1003 + EXPECT_DOUBLE_EQ(x[2], -3.54);
  1004 +}
  1005 +
  1006 +static_assert(!CLI::detail::is_tuple_like<std::vector<double>>::value, "vector should not be like a tuple");
  1007 +static_assert(CLI::detail::is_tuple_like<std::pair<double, double>>::value, "pair of double should be like a tuple");
  1008 +static_assert(CLI::detail::is_tuple_like<std::array<double, 4>>::value, "std::array should be like a tuple");
  1009 +static_assert(!CLI::detail::is_tuple_like<std::string>::value, "std::string should not be like a tuple");
  1010 +static_assert(!CLI::detail::is_tuple_like<double>::value, "double should not be like a tuple");
  1011 +static_assert(CLI::detail::is_tuple_like<std::tuple<double, int, double>>::value, "tuple should look like a tuple");
  1012 +
  1013 +TEST(Types, LexicalConversionTuple2) {
  1014 + CLI::results_t input = {"9.12", "19"};
  1015 +
  1016 + std::tuple<double, int> x;
  1017 + static_assert(CLI::detail::is_tuple_like<decltype(x)>::value,
  1018 + "tuple type must have is_tuple_like trait to be true");
  1019 + bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1020 + EXPECT_TRUE(res);
  1021 + EXPECT_EQ(std::get<1>(x), 19);
  1022 + EXPECT_DOUBLE_EQ(std::get<0>(x), 9.12);
  1023 +
  1024 + input = {"19", "9.12"};
  1025 + res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1026 + EXPECT_FALSE(res);
  1027 +}
  1028 +
  1029 +TEST(Types, LexicalConversionTuple3) {
  1030 + CLI::results_t input = {"9.12", "19", "hippo"};
  1031 + std::tuple<double, int, std::string> x;
  1032 + bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1033 + EXPECT_TRUE(res);
  1034 + EXPECT_EQ(std::get<1>(x), 19);
  1035 + EXPECT_DOUBLE_EQ(std::get<0>(x), 9.12);
  1036 + EXPECT_EQ(std::get<2>(x), "hippo");
  1037 +
  1038 + input = {"19", "9.12"};
  1039 + res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1040 + EXPECT_FALSE(res);
  1041 +}
  1042 +
  1043 +TEST(Types, LexicalConversionTuple4) {
  1044 + CLI::results_t input = {"9.12", "19", "18.6", "5.87"};
  1045 + std::array<double, 4> x;
  1046 + bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1047 + EXPECT_TRUE(res);
  1048 + EXPECT_DOUBLE_EQ(std::get<1>(x), 19);
  1049 + EXPECT_DOUBLE_EQ(x[0], 9.12);
  1050 + EXPECT_DOUBLE_EQ(x[2], 18.6);
  1051 + EXPECT_DOUBLE_EQ(x[3], 5.87);
  1052 +
  1053 + input = {"19", "9.12", "hippo"};
  1054 + res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1055 + EXPECT_FALSE(res);
  1056 +}
  1057 +
  1058 +TEST(Types, LexicalConversionTuple5) {
  1059 + CLI::results_t input = {"9", "19", "18", "5", "235235"};
  1060 + std::array<unsigned int, 5> x;
  1061 + bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1062 + EXPECT_TRUE(res);
  1063 + EXPECT_EQ(std::get<1>(x), 19u);
  1064 + EXPECT_EQ(x[0], 9u);
  1065 + EXPECT_EQ(x[2], 18u);
  1066 + EXPECT_EQ(x[3], 5u);
  1067 + EXPECT_EQ(x[4], 235235u);
  1068 +
  1069 + input = {"19", "9.12", "hippo"};
  1070 + res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1071 + EXPECT_FALSE(res);
  1072 +}
  1073 +
  1074 +TEST(Types, LexicalConversionXomplwz) {
  1075 + CLI::results_t input = {"5.1", "3.5"};
  1076 + std::complex<double> x;
  1077 + bool res = CLI::detail::lexical_conversion<std::complex<double>, std::array<double, 2>>(input, x);
  1078 + EXPECT_TRUE(res);
  1079 + EXPECT_EQ(x.real(), 5.1);
  1080 + EXPECT_EQ(x.imag(), 3.5);
  1081 +}
  1082 +
909 TEST(FixNewLines, BasicCheck) { 1083 TEST(FixNewLines, BasicCheck) {
910 std::string input = "one\ntwo"; 1084 std::string input = "one\ntwo";
911 std::string output = "one\n; two"; 1085 std::string output = "one\n; two";
tests/NewParseTest.cpp
@@ -130,15 +130,20 @@ TEST_F(TApp, BuiltinComplexFail) { @@ -130,15 +130,20 @@ TEST_F(TApp, BuiltinComplexFail) {
130 EXPECT_THROW(run(), CLI::ArgumentMismatch); 130 EXPECT_THROW(run(), CLI::ArgumentMismatch);
131 } 131 }
132 132
  133 +class spair {
  134 + public:
  135 + spair() = default;
  136 + spair(const std::string &s1, const std::string &s2) : first(s1), second(s2) {}
  137 + std::string first;
  138 + std::string second;
  139 +};
133 // an example of custom converter that can be used to add new parsing options 140 // an example of custom converter that can be used to add new parsing options
134 // On MSVC and possibly some other new compilers this can be a free standing function without the template 141 // On MSVC and possibly some other new compilers this can be a free standing function without the template
135 // specialization but this is compiler dependent 142 // specialization but this is compiler dependent
136 namespace CLI { 143 namespace CLI {
137 namespace detail { 144 namespace detail {
138 145
139 -template <>  
140 -bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,  
141 - std::pair<std::string, std::string> &output) { 146 +template <> bool lexical_cast<spair>(const std::string &input, spair &output) {
142 147
143 auto sep = input.find_first_of(':'); 148 auto sep = input.find_first_of(':');
144 if((sep == std::string::npos) && (sep > 0)) { 149 if((sep == std::string::npos) && (sep > 0)) {
@@ -151,7 +156,7 @@ bool lexical_cast&lt;std::pair&lt;std::string, std::string&gt;&gt;(const std::string &amp;input, @@ -151,7 +156,7 @@ bool lexical_cast&lt;std::pair&lt;std::string, std::string&gt;&gt;(const std::string &amp;input,
151 } // namespace CLI 156 } // namespace CLI
152 157
153 TEST_F(TApp, custom_string_converter) { 158 TEST_F(TApp, custom_string_converter) {
154 - std::pair<std::string, std::string> val; 159 + spair val;
155 app.add_option("-d,--dual_string", val); 160 app.add_option("-d,--dual_string", val);
156 161
157 args = {"-d", "string1:string2"}; 162 args = {"-d", "string1:string2"};
@@ -162,7 +167,7 @@ TEST_F(TApp, custom_string_converter) { @@ -162,7 +167,7 @@ TEST_F(TApp, custom_string_converter) {
162 } 167 }
163 168
164 TEST_F(TApp, custom_string_converterFail) { 169 TEST_F(TApp, custom_string_converterFail) {
165 - std::pair<std::string, std::string> val; 170 + spair val;
166 app.add_option("-d,--dual_string", val); 171 app.add_option("-d,--dual_string", val);
167 172
168 args = {"-d", "string2"}; 173 args = {"-d", "string2"};