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 194 app.add_option(option_name, help_str="") // πŸ†•
195 195  
196 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 198 help_string="")
199 199  
200 200 app.add_option_function<type>(option_name,
... ... @@ -202,7 +202,7 @@ app.add_option_function&lt;type&gt;(option_name,
202 202 help_string="")
203 203  
204 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 206 app.add_option<typename T, typename XC>(option_name,
207 207 T &output, // output must be assignable or constructible from a value of type XC
208 208 help_string="")
... ... @@ -212,7 +212,7 @@ app.add_flag(option_name,
212 212 help_string="")
213 213  
214 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 216 help_string="")
217 217  
218 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 249 double val
250 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 254 using vtype=std::variant<int, double, std::string>;
255 255 vtype v1;
... ... @@ -261,6 +261,13 @@ otherwise the output would default to a string. The add_option can be used with
261 261  
262 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 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 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 468  
469 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 472 Option *add_option(std::string option_name,
475 473 T &variable, ///< The variable to set
476 474 std::string option_description = "",
477 475 bool defaulted = false) {
478 476  
479 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 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 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 490 return opt;
489 491 }
490 492  
491 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 495 Option *add_option_function(std::string option_name,
494 496 const std::function<void(const T &)> &func, ///< the callback to execute
495 497 std::string option_description = "") {
496 498  
497 499 auto fun = [func](CLI::results_t res) {
498 500 T variable;
499   - bool result = detail::lexical_cast(res[0], variable);
  501 + bool result = detail::lexical_conversion<T, T>(res, variable);
500 502 if(result) {
501 503 func(variable);
502 504 }
... ... @@ -505,6 +507,7 @@ class App {
505 507  
506 508 Option *opt = add_option(option_name, std::move(fun), option_description, false);
507 509 opt->type_name(detail::type_name<T>());
  510 + opt->type_size(detail::type_count<T>::value);
508 511 return opt;
509 512 }
510 513  
... ... @@ -521,65 +524,6 @@ class App {
521 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 527 /// Set a help flag, replace the existing one if present
584 528 Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
585 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 142 template <typename, typename> static auto test(...) -> std::false_type;
143 143  
144 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 147 #ifdef __GNUC__
148 148 #pragma GCC diagnostic pop
... ... @@ -158,7 +158,7 @@ template &lt;typename T, typename S = std::ostringstream&gt; class is_ostreamable {
158 158 template <typename, typename> static auto test(...) -> std::false_type;
159 159  
160 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 164 /// Check for input streamability
... ... @@ -169,7 +169,7 @@ template &lt;typename T, typename S = std::istringstream&gt; class is_istreamable {
169 169 template <typename, typename> static auto test(...) -> std::false_type;
170 170  
171 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 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 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 201 /// Convert an object to a string (directly forward if this can become a string)
190 202 template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
191 203 auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
... ... @@ -204,12 +216,30 @@ std::string to_string(T &amp;&amp;value) {
204 216  
205 217 /// If conversion is not supported, return an empty string (streaming is not supported for that type)
206 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 222 std::string to_string(T &&) {
210 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 243 /// special template overload
214 244 template <typename T1,
215 245 typename T2,
... ... @@ -228,6 +258,25 @@ std::string checked_to_string(T &amp;&amp;) {
228 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 280 // Enumeration of the different supported categorizations of objects
232 281 enum objCategory : int {
233 282 integral_value = 2,
... ... @@ -239,6 +288,7 @@ enum objCategory : int {
239 288 double_constructible = 14,
240 289 integer_constructible = 16,
241 290 vector_value = 30,
  291 + tuple_value = 35,
242 292 // string assignable or greater used in a condition so anything string like must come last
243 293 string_assignable = 50,
244 294 string_constructible = 60,
... ... @@ -308,36 +358,49 @@ template &lt;typename T&gt; struct uncommon_type {
308 358 !std::is_enum<T>::value,
309 359 std::true_type,
310 360 std::false_type>::type;
311   - static const bool value = type::value;
  361 + static constexpr bool value = type::value;
312 362 };
313 363  
314 364 /// Assignable from double or int
315 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 370 static constexpr objCategory value{number_constructible};
320 371 };
321 372  
322 373 /// Assignable from int
323 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 382 /// Assignable from double
331 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 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 406 // Type name print
... ... @@ -367,11 +430,6 @@ constexpr const char *type_name() {
367 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 433 /// Print name for enumeration types
376 434 template <typename T, enable_if_t<classify_object<T>::value == enumeration, detail::enabler> = detail::dummy>
377 435 constexpr const char *type_name() {
... ... @@ -390,6 +448,60 @@ constexpr const char *type_name() {
390 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 505 // Lexical cast
394 506  
395 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 686 }
575 687  
576 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 690 bool lexical_assign(const std::string &input, T &output) {
579 691 return lexical_cast(input, output);
580 692 }
581 693  
582 694 /// Assign a value converted from a string in lexical cast to the output value directly
583 695 template <
584   - class T,
585   - class XC,
  696 + typename T,
  697 + typename XC,
586 698 enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
587 699 bool lexical_assign(const std::string &input, T &output) {
588 700 XC val;
589   - auto parse_result = lexical_cast<XC>(input, val);
  701 + bool parse_result = lexical_cast<XC>(input, val);
590 702 if(parse_result) {
591 703 output = val;
592 704 }
... ... @@ -594,8 +706,8 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) {
594 706 }
595 707  
596 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 711 enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value &&
600 712 std::is_move_assignable<T>::value,
601 713 detail::enabler> = detail::dummy>
... ... @@ -607,6 +719,160 @@ bool lexical_assign(const std::string &amp;input, T &amp;output) {
607 719 }
608 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 877 /// Sum a vector of flag representations
612 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 1 #include "app_helper.hpp"
2 2  
  3 +#include <array>
3 4 #include <climits>
4 5 #include <complex>
5 6 #include <cstdint>
6 7 #include <cstdio>
7 8 #include <fstream>
8 9 #include <string>
  10 +#include <tuple>
9 11  
10 12 class NotStreamable {};
11 13  
... ... @@ -25,6 +27,30 @@ TEST(TypeTools, Streaming) {
25 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 54 TEST(Split, SimpleByToken) {
29 55 auto out = CLI::detail::split("one.two.three", '.');
30 56 ASSERT_EQ(3u, out.size());
... ... @@ -782,7 +808,33 @@ TEST(Types, TypeName) {
782 808 EXPECT_EQ("FLOAT", float_name);
783 809  
784 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 839 std::string text_name = CLI::detail::type_name<std::string>();
788 840 EXPECT_EQ("TEXT", text_name);
... ... @@ -793,6 +845,13 @@ TEST(Types, TypeName) {
793 845 enum class test { test1, test2, test3 };
794 846 std::string enum_name = CLI::detail::type_name<test>();
795 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 857 TEST(Types, OverflowSmall) {
... ... @@ -906,6 +965,121 @@ TEST(Types, LexicalCastEnum) {
906 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 1083 TEST(FixNewLines, BasicCheck) {
910 1084 std::string input = "one\ntwo";
911 1085 std::string output = "one\n; two";
... ...
tests/NewParseTest.cpp
... ... @@ -130,15 +130,20 @@ TEST_F(TApp, BuiltinComplexFail) {
130 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 140 // an example of custom converter that can be used to add new parsing options
134 141 // On MSVC and possibly some other new compilers this can be a free standing function without the template
135 142 // specialization but this is compiler dependent
136 143 namespace CLI {
137 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 148 auto sep = input.find_first_of(':');
144 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 156 } // namespace CLI
152 157  
153 158 TEST_F(TApp, custom_string_converter) {
154   - std::pair<std::string, std::string> val;
  159 + spair val;
155 160 app.add_option("-d,--dual_string", val);
156 161  
157 162 args = {"-d", "string1:string2"};
... ... @@ -162,7 +167,7 @@ TEST_F(TApp, custom_string_converter) {
162 167 }
163 168  
164 169 TEST_F(TApp, custom_string_converterFail) {
165   - std::pair<std::string, std::string> val;
  170 + spair val;
166 171 app.add_option("-d,--dual_string", val);
167 172  
168 173 args = {"-d", "string2"};
... ...