Commit eab92ed988b29c7566f9606d624a1b1c399b9894

Authored by Philip Top
Committed by Henry Schreiner
1 parent dbd49335

modified option template (#285)

* 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

* fix case when string_view doesn't work to append to a string.

* This PR also addressed #300

* modify lexical_cast to take  const std::string &, instead of by value to allow string_view in a few cases
.appveyor.yml
  1 +version: 1.8.0.{build}
  2 +
1 branches: 3 branches:
2 only: 4 only:
3 - master 5 - master
README.md
@@ -55,7 +55,7 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature @@ -55,7 +55,7 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
55 - [Contribute](#contribute) 55 - [Contribute](#contribute)
56 - [License](#license) 56 - [License](#license)
57 57
58 -Features that were added in the last released major version are marked with "πŸ†•". Features only available in master are marked with "πŸ†•". 58 +Features that were added in the last released major version are marked with "πŸ†•". Features only available in master are marked with "🚧".
59 59
60 ## Background 60 ## Background
61 61
@@ -194,21 +194,25 @@ While all options internally are the same type, there are several ways to add an @@ -194,21 +194,25 @@ 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 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.
198 help_string="") 198 help_string="")
199 199
200 app.add_option_function<type>(option_name, 200 app.add_option_function<type>(option_name,
201 - function <void(const type &value)>, // πŸ†• int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string 201 + function <void(const type &value)>, // πŸ†• int, bool, float, enum, or string-like, or anything with a defined conversion from a string, or a vector of any of the previous objects.
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.
  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
  208 + help_string="")
205 209
206 // Add flags 210 // Add flags
207 app.add_flag(option_name, 211 app.add_flag(option_name,
208 help_string="") 212 help_string="")
209 213
210 app.add_flag(option_name, 214 app.add_flag(option_name,
211 - variable_to_bind_to, // bool, int, πŸ†• float, πŸ†• vector, πŸ†• enum, or πŸ†• string-like, or πŸ†• anything with a defined conversion from a string 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
212 help_string="") 216 help_string="")
213 217
214 app.add_flag_function(option_name, // πŸ†• 218 app.add_flag_function(option_name, // πŸ†•
@@ -240,6 +244,25 @@ An option name must start with a alphabetic character, underscore, a number πŸ†• @@ -240,6 +244,25 @@ An option name must start with a alphabetic character, underscore, a number πŸ†•
240 244
241 The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid. 245 The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid.
242 246
  247 +🚧 The two parameter template overload can be used in cases where you want to restrict the input such as
  248 +```
  249 +double val
  250 +app.add_option<double,unsigned int>("-v",val);
  251 +```
  252 +which would first verify the input is convertible to an int before assigning it. Or using some variant type
  253 +```
  254 +using vtype=std::variant<int, double, std::string>;
  255 + vtype v1;
  256 +app.add_option<vtype,std:string>("--vs",v1);
  257 +app.add_option<vtype,int>("--vi",v1);
  258 +app.add_option<vtype,double>("--vf",v1);
  259 +```
  260 +otherwise the output would default to a string. The add_option can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.
  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.
  263 +
  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.
  265 +
243 πŸ†• 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: 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:
244 267
245 ```cpp 268 ```cpp
@@ -263,8 +286,6 @@ using any of those flags on the command line will result in the specified number @@ -263,8 +286,6 @@ using any of those flags on the command line will result in the specified number
263 286
264 On a `C++14` compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. 287 On a `C++14` compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.
265 288
266 -On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals][] for information on how this was done and how you can add your own converters. Optional values are only supported for types that support the `>>` operator.  
267 -  
268 #### Example 289 #### Example
269 290
270 - `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` 291 - `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one`
@@ -300,8 +321,8 @@ Before parsing, you can set the following options: @@ -300,8 +321,8 @@ Before parsing, you can set the following options:
300 - `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered. 321 - `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered.
301 - `->configurable(false)`: Disable this option from being in a configuration file. 322 - `->configurable(false)`: Disable this option from being in a configuration file.
302 `->capture_default_str()`: πŸ†• Store the current value attached and display it in the help string. 323 `->capture_default_str()`: πŸ†• Store the current value attached and display it in the help string.
303 - `->default_function(std::string())`: πŸ†• Advanced: Change the function that `capture_default_str()` uses.  
304 - `->always_capture_default()`: πŸ†• Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. 324 +- `->default_function(std::string())`: πŸ†• Advanced: Change the function that `capture_default_str()` uses.
  325 +- `->always_capture_default()`: πŸ†• Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
305 326
306 327
307 These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. 328 These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
include/CLI/App.hpp
@@ -4,13 +4,11 @@ @@ -4,13 +4,11 @@
4 // file LICENSE or https://github.com/CLIUtils/CLI11 for details. 4 // file LICENSE or https://github.com/CLIUtils/CLI11 for details.
5 5
6 #include <algorithm> 6 #include <algorithm>
7 -#include <deque>  
8 #include <functional> 7 #include <functional>
9 #include <iostream> 8 #include <iostream>
10 #include <iterator> 9 #include <iterator>
11 #include <memory> 10 #include <memory>
12 #include <numeric> 11 #include <numeric>
13 -#include <set>  
14 #include <sstream> 12 #include <sstream>
15 #include <string> 13 #include <string>
16 #include <utility> 14 #include <utility>
@@ -469,18 +467,23 @@ class App { @@ -469,18 +467,23 @@ class App {
469 } 467 }
470 468
471 /// 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`)
472 - template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy> 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>
473 Option *add_option(std::string option_name, 474 Option *add_option(std::string option_name,
474 T &variable, ///< The variable to set 475 T &variable, ///< The variable to set
475 std::string option_description = "", 476 std::string option_description = "",
476 bool defaulted = false) { 477 bool defaulted = false) {
477 478
478 - auto fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; 479 + auto fun = [&variable](CLI::results_t res) { // comment for spacing
  480 + return detail::lexical_assign<T, XC>(res[0], variable);
  481 + };
479 482
480 Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { 483 Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
481 - return std::string(CLI::detail::to_string(variable)); 484 + return std::string(CLI::detail::checked_to_string<T, XC>(variable));
482 }); 485 });
483 - opt->type_name(detail::type_name<T>()); 486 + opt->type_name(detail::type_name<XC>());
484 487
485 return opt; 488 return opt;
486 } 489 }
include/CLI/CLI.hpp
@@ -10,8 +10,6 @@ @@ -10,8 +10,6 @@
10 10
11 #include "CLI/Macros.hpp" 11 #include "CLI/Macros.hpp"
12 12
13 -#include "CLI/Optional.hpp"  
14 -  
15 #include "CLI/StringTools.hpp" 13 #include "CLI/StringTools.hpp"
16 14
17 #include "CLI/Error.hpp" 15 #include "CLI/Error.hpp"
include/CLI/Optional.hpp deleted
1 -#pragma once  
2 -  
3 -// Distributed under the 3-Clause BSD License. See accompanying  
4 -// file LICENSE or https://github.com/CLIUtils/CLI11 for details.  
5 -  
6 -#include <istream>  
7 -  
8 -#include "CLI/Macros.hpp"  
9 -  
10 -// [CLI11:verbatim]  
11 -  
12 -// You can explicitly enable or disable support  
13 -// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too.  
14 -// We nest the check for __has_include and it's usage  
15 -#ifndef CLI11_STD_OPTIONAL  
16 -#ifdef __has_include  
17 -#if defined(CLI11_CPP17) && __has_include(<optional>)  
18 -#define CLI11_STD_OPTIONAL 1  
19 -#else  
20 -#define CLI11_STD_OPTIONAL 0  
21 -#endif  
22 -#else  
23 -#define CLI11_STD_OPTIONAL 0  
24 -#endif  
25 -#endif  
26 -  
27 -#ifndef CLI11_EXPERIMENTAL_OPTIONAL  
28 -#define CLI11_EXPERIMENTAL_OPTIONAL 0  
29 -#endif  
30 -  
31 -#ifndef CLI11_BOOST_OPTIONAL  
32 -#define CLI11_BOOST_OPTIONAL 0  
33 -#endif  
34 -  
35 -#if CLI11_BOOST_OPTIONAL  
36 -#include <boost/version.hpp>  
37 -#if BOOST_VERSION < 106100  
38 -#error "This boost::optional version is not supported, use 1.61 or better"  
39 -#endif  
40 -#endif  
41 -  
42 -#if CLI11_STD_OPTIONAL  
43 -#include <optional>  
44 -#endif  
45 -#if CLI11_EXPERIMENTAL_OPTIONAL  
46 -#include <experimental/optional>  
47 -#endif  
48 -#if CLI11_BOOST_OPTIONAL  
49 -#include <boost/optional.hpp>  
50 -#include <boost/optional/optional_io.hpp>  
51 -#endif  
52 -// [CLI11:verbatim]  
53 -  
54 -namespace CLI {  
55 -  
56 -#if CLI11_STD_OPTIONAL  
57 -template <typename T> std::istream &operator>>(std::istream &in, std::optional<T> &val) {  
58 - T v;  
59 - in >> v;  
60 - val = v;  
61 - return in;  
62 -}  
63 -#endif  
64 -  
65 -#if CLI11_EXPERIMENTAL_OPTIONAL  
66 -template <typename T> std::istream &operator>>(std::istream &in, std::experimental::optional<T> &val) {  
67 - T v;  
68 - in >> v;  
69 - val = v;  
70 - return in;  
71 -}  
72 -#endif  
73 -  
74 -#if CLI11_BOOST_OPTIONAL  
75 -template <typename T> std::istream &operator>>(std::istream &in, boost::optional<T> &val) {  
76 - T v;  
77 - in >> v;  
78 - val = v;  
79 - return in;  
80 -}  
81 -#endif  
82 -  
83 -// Export the best optional to the CLI namespace  
84 -#if CLI11_STD_OPTIONAL  
85 -using std::optional;  
86 -#elif CLI11_EXPERIMENTAL_OPTIONAL  
87 -using std::experimental::optional;  
88 -#elif CLI11_BOOST_OPTIONAL  
89 -using boost::optional;  
90 -#endif  
91 -  
92 -// This is true if any optional is found  
93 -#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL  
94 -#define CLI11_OPTIONAL 1  
95 -#endif  
96 -  
97 -} // namespace CLI  
include/CLI/StringTools.hpp
@@ -25,14 +25,6 @@ std::ostream &amp;operator&lt;&lt;(std::ostream &amp;in, const T &amp;item) { @@ -25,14 +25,6 @@ std::ostream &amp;operator&lt;&lt;(std::ostream &amp;in, const T &amp;item) {
25 return in << static_cast<typename std::underlying_type<T>::type>(item); 25 return in << static_cast<typename std::underlying_type<T>::type>(item);
26 } 26 }
27 27
28 -/// input streaming for enumerations  
29 -template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>  
30 -std::istream &operator>>(std::istream &in, T &item) {  
31 - typename std::underlying_type<T>::type i;  
32 - in >> i;  
33 - item = static_cast<T>(i);  
34 - return in;  
35 -}  
36 } // namespace enums 28 } // namespace enums
37 29
38 /// Export to CLI namespace 30 /// Export to CLI namespace
@@ -57,17 +49,6 @@ inline std::vector&lt;std::string&gt; split(const std::string &amp;s, char delim) { @@ -57,17 +49,6 @@ inline std::vector&lt;std::string&gt; split(const std::string &amp;s, char delim) {
57 } 49 }
58 return elems; 50 return elems;
59 } 51 }
60 -/// simple utility to convert various types to a string  
61 -template <typename T> inline std::string as_string(const T &v) {  
62 - std::ostringstream s;  
63 - s << v;  
64 - return s.str();  
65 -}  
66 -// if the data type is already a string just forward it  
67 -template <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type>  
68 -inline auto as_string(T &&v) -> decltype(std::forward<T>(v)) {  
69 - return std::forward<T>(v);  
70 -}  
71 52
72 /// Simple function to join a string 53 /// Simple function to join a string
73 template <typename T> std::string join(const T &v, std::string delim = ",") { 54 template <typename T> std::string join(const T &v, std::string delim = ",") {
include/CLI/TypeTools.hpp
@@ -10,17 +10,6 @@ @@ -10,17 +10,6 @@
10 #include <type_traits> 10 #include <type_traits>
11 #include <vector> 11 #include <vector>
12 12
13 -// [CLI11:verbatim]  
14 -#if defined(CLI11_CPP17)  
15 -#if defined(__has_include)  
16 -#if __has_include(<string_view>)  
17 -#include <string_view>  
18 -#define CLI11_HAS_STRING_VIEW  
19 -#endif  
20 -#endif  
21 -#endif  
22 -// [CLI11:verbatim]  
23 -  
24 namespace CLI { 13 namespace CLI {
25 14
26 // Type tools 15 // Type tools
@@ -83,10 +72,6 @@ template &lt;typename T&gt; struct IsMemberType { using type = T; }; @@ -83,10 +72,6 @@ template &lt;typename T&gt; struct IsMemberType { using type = T; };
83 /// The main custom type needed here is const char * should be a string. 72 /// The main custom type needed here is const char * should be a string.
84 template <> struct IsMemberType<const char *> { using type = std::string; }; 73 template <> struct IsMemberType<const char *> { using type = std::string; };
85 74
86 -#ifdef CLI11_HAS_STRING_VIEW  
87 -template <> struct IsMemberType<std::string_view> { using type = std::string; };  
88 -#endif  
89 -  
90 namespace detail { 75 namespace detail {
91 76
92 // These are utilities for IsMember 77 // These are utilities for IsMember
@@ -139,19 +124,68 @@ struct pair_adaptor&lt; @@ -139,19 +124,68 @@ struct pair_adaptor&lt;
139 } 124 }
140 }; 125 };
141 126
142 -// Check for streamability 127 +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning
  128 +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in
  129 +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a
  130 +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out.
  131 +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be
  132 +// suppressed
  133 +#ifdef __GNUC__
  134 +#pragma GCC diagnostic push
  135 +#pragma GCC diagnostic ignored "-Wnarrowing"
  136 +#endif
  137 +// check for constructibility from a specific type and copy assignable used in the parse detection
  138 +template <typename T, typename C> class is_direct_constructible {
  139 + template <typename TT, typename CC>
  140 + static auto test(int) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
  141 +
  142 + template <typename, typename> static auto test(...) -> std::false_type;
  143 +
  144 + public:
  145 + static const bool value = decltype(test<T, C>(0))::value;
  146 +};
  147 +#ifdef __GNUC__
  148 +#pragma GCC diagnostic pop
  149 +#endif
  150 +
  151 +// Check for output streamability
143 // Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream 152 // Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
144 153
145 -template <typename S, typename T> class is_streamable {  
146 - template <typename SS, typename TT> 154 +template <typename T, typename S = std::ostringstream> class is_ostreamable {
  155 + template <typename TT, typename SS>
147 static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type()); 156 static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
148 157
149 template <typename, typename> static auto test(...) -> std::false_type; 158 template <typename, typename> static auto test(...) -> std::false_type;
150 159
151 public: 160 public:
152 - static const bool value = decltype(test<S, T>(0))::value; 161 + static const bool value = decltype(test<T, S>(0))::value;
  162 +};
  163 +
  164 +/// Check for input streamability
  165 +template <typename T, typename S = std::istringstream> class is_istreamable {
  166 + template <typename TT, typename SS>
  167 + static auto test(int) -> decltype(std::declval<SS &>() >> std::declval<TT &>(), std::true_type());
  168 +
  169 + template <typename, typename> static auto test(...) -> std::false_type;
  170 +
  171 + public:
  172 + static const bool value = decltype(test<T, S>(0))::value;
153 }; 173 };
154 174
  175 +/// Templated operation to get a value from a stream
  176 +template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
  177 +bool from_stream(const std::string &istring, T &obj) {
  178 + std::istringstream is;
  179 + is.str(istring);
  180 + is >> obj;
  181 + return !is.fail() && !is.rdbuf()->in_avail();
  182 +}
  183 +
  184 +template <typename T, enable_if_t<!is_istreamable<T>::value, detail::enabler> = detail::dummy>
  185 +bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
  186 + return false;
  187 +}
  188 +
155 /// Convert an object to a string (directly forward if this can become a string) 189 /// Convert an object to a string (directly forward if this can become a string)
156 template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy> 190 template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
157 auto to_string(T &&value) -> decltype(std::forward<T>(value)) { 191 auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
@@ -160,8 +194,8 @@ auto to_string(T &amp;&amp;value) -&gt; decltype(std::forward&lt;T&gt;(value)) { @@ -160,8 +194,8 @@ auto to_string(T &amp;&amp;value) -&gt; decltype(std::forward&lt;T&gt;(value)) {
160 194
161 /// Convert an object to a string (streaming must be supported for that type) 195 /// Convert an object to a string (streaming must be supported for that type)
162 template <typename T, 196 template <typename T,
163 - enable_if_t<!std::is_constructible<std::string, T>::value && is_streamable<std::stringstream, T>::value,  
164 - detail::enabler> = detail::dummy> 197 + enable_if_t<!std::is_constructible<std::string, T>::value && is_ostreamable<T>::value, detail::enabler> =
  198 + detail::dummy>
165 std::string to_string(T &&value) { 199 std::string to_string(T &&value) {
166 std::stringstream stream; 200 std::stringstream stream;
167 stream << value; 201 stream << value;
@@ -170,12 +204,30 @@ std::string to_string(T &amp;&amp;value) { @@ -170,12 +204,30 @@ std::string to_string(T &amp;&amp;value) {
170 204
171 /// If conversion is not supported, return an empty string (streaming is not supported for that type) 205 /// If conversion is not supported, return an empty string (streaming is not supported for that type)
172 template <typename T, 206 template <typename T,
173 - enable_if_t<!std::is_constructible<std::string, T>::value && !is_streamable<std::stringstream, T>::value,  
174 - detail::enabler> = detail::dummy> 207 + enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value, detail::enabler> =
  208 + detail::dummy>
175 std::string to_string(T &&) { 209 std::string to_string(T &&) {
176 return std::string{}; 210 return std::string{};
177 } 211 }
178 212
  213 +/// special template overload
  214 +template <typename T1,
  215 + typename T2,
  216 + typename T,
  217 + enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
  218 +auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) {
  219 + return to_string(std::forward<T>(value));
  220 +}
  221 +
  222 +/// special template overload
  223 +template <typename T1,
  224 + typename T2,
  225 + typename T,
  226 + enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
  227 +std::string checked_to_string(T &&) {
  228 + return std::string{};
  229 +}
  230 +
179 // Type name print 231 // Type name print
180 232
181 /// Was going to be based on 233 /// Was going to be based on
@@ -277,7 +329,7 @@ template &lt; @@ -277,7 +329,7 @@ template &lt;
277 typename T, 329 typename T,
278 enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value, 330 enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value,
279 detail::enabler> = detail::dummy> 331 detail::enabler> = detail::dummy>
280 -bool lexical_cast(std::string input, T &output) { 332 +bool lexical_cast(const std::string &input, T &output) {
281 try { 333 try {
282 size_t n = 0; 334 size_t n = 0;
283 long long output_ll = std::stoll(input, &n, 0); 335 long long output_ll = std::stoll(input, &n, 0);
@@ -294,7 +346,7 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -294,7 +346,7 @@ bool lexical_cast(std::string input, T &amp;output) {
294 template <typename T, 346 template <typename T,
295 enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> = 347 enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> =
296 detail::dummy> 348 detail::dummy>
297 -bool lexical_cast(std::string input, T &output) { 349 +bool lexical_cast(const std::string &input, T &output) {
298 if(!input.empty() && input.front() == '-') 350 if(!input.empty() && input.front() == '-')
299 return false; // std::stoull happily converts negative values to junk without any errors. 351 return false; // std::stoull happily converts negative values to junk without any errors.
300 352
@@ -312,7 +364,7 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -312,7 +364,7 @@ bool lexical_cast(std::string input, T &amp;output) {
312 364
313 /// Boolean values 365 /// Boolean values
314 template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> 366 template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
315 -bool lexical_cast(std::string input, T &output) { 367 +bool lexical_cast(const std::string &input, T &output) {
316 try { 368 try {
317 auto out = to_flag_value(input); 369 auto out = to_flag_value(input);
318 output = (out > 0); 370 output = (out > 0);
@@ -324,7 +376,7 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -324,7 +376,7 @@ bool lexical_cast(std::string input, T &amp;output) {
324 376
325 /// Floats 377 /// Floats
326 template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> 378 template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
327 -bool lexical_cast(std::string input, T &output) { 379 +bool lexical_cast(const std::string &input, T &output) {
328 try { 380 try {
329 size_t n = 0; 381 size_t n = 0;
330 output = static_cast<T>(std::stold(input, &n)); 382 output = static_cast<T>(std::stold(input, &n));
@@ -336,19 +388,29 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -336,19 +388,29 @@ bool lexical_cast(std::string input, T &amp;output) {
336 } 388 }
337 } 389 }
338 390
339 -/// String and similar 391 +/// String and similar direct assignment
340 template <typename T, 392 template <typename T,
341 enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && 393 enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
342 std::is_assignable<T &, std::string>::value, 394 std::is_assignable<T &, std::string>::value,
343 detail::enabler> = detail::dummy> 395 detail::enabler> = detail::dummy>
344 -bool lexical_cast(std::string input, T &output) { 396 +bool lexical_cast(const std::string &input, T &output) {
345 output = input; 397 output = input;
346 return true; 398 return true;
347 } 399 }
348 400
  401 +/// String and similar constructible and copy assignment
  402 +template <typename T,
  403 + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
  404 + !std::is_assignable<T &, std::string>::value && std::is_constructible<T, std::string>::value,
  405 + detail::enabler> = detail::dummy>
  406 +bool lexical_cast(const std::string &input, T &output) {
  407 + output = T(input);
  408 + return true;
  409 +}
  410 +
349 /// Enumerations 411 /// Enumerations
350 template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> 412 template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
351 -bool lexical_cast(std::string input, T &output) { 413 +bool lexical_cast(const std::string &input, T &output) {
352 typename std::underlying_type<T>::type val; 414 typename std::underlying_type<T>::type val;
353 bool retval = detail::lexical_cast(input, val); 415 bool retval = detail::lexical_cast(input, val);
354 if(!retval) { 416 if(!retval) {
@@ -358,21 +420,112 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -358,21 +420,112 @@ bool lexical_cast(std::string input, T &amp;output) {
358 return true; 420 return true;
359 } 421 }
360 422
361 -/// Non-string parsable 423 +/// Assignable from double or int
362 template <typename T, 424 template <typename T,
363 enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && 425 enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
364 - !std::is_assignable<T &, std::string>::value && !std::is_enum<T>::value, 426 + !std::is_assignable<T &, std::string>::value &&
  427 + !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
  428 + is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value,
365 detail::enabler> = detail::dummy> 429 detail::enabler> = detail::dummy>
366 -bool lexical_cast(std::string input, T &output) {  
367 - std::istringstream is; 430 +bool lexical_cast(const std::string &input, T &output) {
  431 + int val;
  432 + if(lexical_cast(input, val)) {
  433 + output = T(val);
  434 + return true;
  435 + } else {
  436 + double dval;
  437 + if(lexical_cast(input, dval)) {
  438 + output = T{dval};
  439 + return true;
  440 + }
  441 + }
  442 + return from_stream(input, output);
  443 +}
368 444
369 - is.str(input);  
370 - is >> output;  
371 - return !is.fail() && !is.rdbuf()->in_avail(); 445 +/// Assignable from int64
  446 +template <typename T,
  447 + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
  448 + !std::is_assignable<T &, std::string>::value &&
  449 + !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
  450 + !is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value,
  451 + detail::enabler> = detail::dummy>
  452 +bool lexical_cast(const std::string &input, T &output) {
  453 + int val;
  454 + if(lexical_cast(input, val)) {
  455 + output = T(val);
  456 + return true;
  457 + }
  458 + return from_stream(input, output);
  459 +}
  460 +
  461 +/// Assignable from double
  462 +template <typename T,
  463 + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
  464 + !std::is_assignable<T &, std::string>::value &&
  465 + !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
  466 + is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value,
  467 + detail::enabler> = detail::dummy>
  468 +bool lexical_cast(const std::string &input, T &output) {
  469 + double val;
  470 + if(lexical_cast(input, val)) {
  471 + output = T{val};
  472 + return true;
  473 + }
  474 + return from_stream(input, output);
  475 +}
  476 +
  477 +/// Non-string parsable by a stream
  478 +template <typename T,
  479 + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
  480 + !std::is_assignable<T &, std::string>::value &&
  481 + !std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
  482 + !is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value,
  483 + detail::enabler> = detail::dummy>
  484 +bool lexical_cast(const std::string &input, T &output) {
  485 + static_assert(is_istreamable<T>::value,
  486 + "option object type must have a lexical cast overload or streaming input operator(>>) defined if it "
  487 + "is convertible from another type use the add_option<T, XC>(...) with XC being the known type");
  488 + return from_stream(input, output);
  489 +}
  490 +
  491 +/// Assign a value through lexical cast operations
  492 +template <class T, class XC, enable_if_t<std::is_same<T, XC>::value, detail::enabler> = detail::dummy>
  493 +bool lexical_assign(const std::string &input, T &output) {
  494 + return lexical_cast(input, output);
  495 +}
  496 +
  497 +/// Assign a value converted from a string in lexical cast to the output value directly
  498 +template <
  499 + class T,
  500 + class XC,
  501 + enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
  502 +bool lexical_assign(const std::string &input, T &output) {
  503 + XC val;
  504 + auto parse_result = lexical_cast<XC>(input, val);
  505 + if(parse_result) {
  506 + output = val;
  507 + }
  508 + return parse_result;
  509 +}
  510 +
  511 +/// Assign a value from a lexical cast through constructing a value and move assigning it
  512 +template <class T,
  513 + class XC,
  514 + enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value &&
  515 + std::is_move_assignable<T>::value,
  516 + detail::enabler> = detail::dummy>
  517 +bool lexical_assign(const std::string &input, T &output) {
  518 + XC val;
  519 + bool parse_result = lexical_cast<XC>(input, val);
  520 + if(parse_result) {
  521 + output = T(val); // use () form of constructor to allow some implicit conversions
  522 + }
  523 + return parse_result;
372 } 524 }
373 525
374 /// Sum a vector of flag representations 526 /// Sum a vector of flag representations
375 -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by 527 +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
  528 +/// by
376 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most 529 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
377 /// common true and false strings then uses stoll to convert the rest for summing 530 /// common true and false strings then uses stoll to convert the rest for summing
378 template <typename T, 531 template <typename T,
@@ -386,7 +539,8 @@ void sum_flag_vector(const std::vector&lt;std::string&gt; &amp;flags, T &amp;output) { @@ -386,7 +539,8 @@ void sum_flag_vector(const std::vector&lt;std::string&gt; &amp;flags, T &amp;output) {
386 } 539 }
387 540
388 /// Sum a vector of flag representations 541 /// Sum a vector of flag representations
389 -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by 542 +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
  543 +/// by
390 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most 544 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
391 /// common true and false strings then uses stoll to convert the rest for summing 545 /// common true and false strings then uses stoll to convert the rest for summing
392 template <typename T, 546 template <typename T,
include/CLI/Validators.hpp
@@ -407,11 +407,11 @@ class Bound : public Validator { @@ -407,11 +407,11 @@ class Bound : public Validator {
407 return "Value " + input + " could not be converted"; 407 return "Value " + input + " could not be converted";
408 } 408 }
409 if(val < min) 409 if(val < min)
410 - input = detail::as_string(min); 410 + input = detail::to_string(min);
411 else if(val > max) 411 else if(val > max)
412 - input = detail::as_string(max); 412 + input = detail::to_string(max);
413 413
414 - return std::string(); 414 + return std::string{};
415 }; 415 };
416 } 416 }
417 417
@@ -451,9 +451,11 @@ template &lt;typename T&gt; std::string generate_map(const T &amp;map, bool key_only = fal @@ -451,9 +451,11 @@ template &lt;typename T&gt; std::string generate_map(const T &amp;map, bool key_only = fal
451 std::string out(1, '{'); 451 std::string out(1, '{');
452 out.append(detail::join(detail::smart_deref(map), 452 out.append(detail::join(detail::smart_deref(map),
453 [key_only](const iteration_type_t &v) { 453 [key_only](const iteration_type_t &v) {
454 - auto res = detail::as_string(detail::pair_adaptor<element_t>::first(v)); 454 + std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
  455 +
455 if(!key_only) { 456 if(!key_only) {
456 - res += "->" + detail::as_string(detail::pair_adaptor<element_t>::second(v)); 457 + res.append("->");
  458 + res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
457 } 459 }
458 return res; 460 return res;
459 }, 461 },
@@ -581,7 +583,7 @@ class IsMember : public Validator { @@ -581,7 +583,7 @@ class IsMember : public Validator {
581 if(res.first) { 583 if(res.first) {
582 // Make sure the version in the input string is identical to the one in the set 584 // Make sure the version in the input string is identical to the one in the set
583 if(filter_fn) { 585 if(filter_fn) {
584 - input = detail::as_string(detail::pair_adaptor<element_t>::first(*(res.second))); 586 + input = detail::to_string(detail::pair_adaptor<element_t>::first(*(res.second)));
585 } 587 }
586 588
587 // Return empty error string (success) 589 // Return empty error string (success)
@@ -649,7 +651,7 @@ class Transformer : public Validator { @@ -649,7 +651,7 @@ class Transformer : public Validator {
649 } 651 }
650 auto res = detail::search(mapping, b, filter_fn); 652 auto res = detail::search(mapping, b, filter_fn);
651 if(res.first) { 653 if(res.first) {
652 - input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second)); 654 + input = detail::to_string(detail::pair_adaptor<element_t>::second(*res.second));
653 } 655 }
654 return std::string{}; 656 return std::string{};
655 }; 657 };
@@ -699,7 +701,7 @@ class CheckedTransformer : public Validator { @@ -699,7 +701,7 @@ class CheckedTransformer : public Validator {
699 out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; 701 out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
700 out += detail::join( 702 out += detail::join(
701 detail::smart_deref(mapping), 703 detail::smart_deref(mapping),
702 - [](const iteration_type_t &v) { return detail::as_string(detail::pair_adaptor<element_t>::second(v)); }, 704 + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
703 ","); 705 ",");
704 out.push_back('}'); 706 out.push_back('}');
705 return out; 707 return out;
@@ -716,12 +718,12 @@ class CheckedTransformer : public Validator { @@ -716,12 +718,12 @@ class CheckedTransformer : public Validator {
716 } 718 }
717 auto res = detail::search(mapping, b, filter_fn); 719 auto res = detail::search(mapping, b, filter_fn);
718 if(res.first) { 720 if(res.first) {
719 - input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second)); 721 + input = detail::to_string(detail::pair_adaptor<element_t>::second(*res.second));
720 return std::string{}; 722 return std::string{};
721 } 723 }
722 } 724 }
723 for(const auto &v : detail::smart_deref(mapping)) { 725 for(const auto &v : detail::smart_deref(mapping)) {
724 - auto output_string = detail::as_string(detail::pair_adaptor<element_t>::second(v)); 726 + auto output_string = detail::to_string(detail::pair_adaptor<element_t>::second(v));
725 if(output_string == input) { 727 if(output_string == input) {
726 return std::string(); 728 return std::string();
727 } 729 }
@@ -832,10 +834,10 @@ class AsNumberWithUnit : public Validator { @@ -832,10 +834,10 @@ class AsNumberWithUnit : public Validator {
832 // perform safe multiplication 834 // perform safe multiplication
833 bool ok = detail::checked_multiply(num, it->second); 835 bool ok = detail::checked_multiply(num, it->second);
834 if(!ok) { 836 if(!ok) {
835 - throw ValidationError(detail::as_string(num) + " multiplied by " + unit + 837 + throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
836 " factor would cause number overflow. Use smaller value."); 838 " factor would cause number overflow. Use smaller value.");
837 } 839 }
838 - input = detail::as_string(num); 840 + input = detail::to_string(num);
839 841
840 return {}; 842 return {};
841 }; 843 };
tests/AppTest.cpp
@@ -669,6 +669,27 @@ TEST_F(TApp, ShortOpts) { @@ -669,6 +669,27 @@ TEST_F(TApp, ShortOpts) {
669 EXPECT_EQ(app.count_all(), 3u); 669 EXPECT_EQ(app.count_all(), 3u);
670 } 670 }
671 671
  672 +TEST_F(TApp, TwoParamTemplateOpts) {
  673 +
  674 + double funnyint;
  675 + auto opt = app.add_option<double, unsigned int>("-y", funnyint);
  676 +
  677 + args = {"-y", "32"};
  678 +
  679 + run();
  680 +
  681 + EXPECT_EQ(32.0, funnyint);
  682 +
  683 + args = {"-y", "32.3"};
  684 + EXPECT_THROW(run(), CLI::ConversionError);
  685 +
  686 + args = {"-y", "-19"};
  687 + EXPECT_THROW(run(), CLI::ConversionError);
  688 +
  689 + opt->capture_default_str();
  690 + EXPECT_TRUE(opt->get_default_str().empty());
  691 +}
  692 +
672 TEST_F(TApp, DefaultOpts) { 693 TEST_F(TApp, DefaultOpts) {
673 694
674 int i = 3; 695 int i = 3;
tests/CreationTest.cpp
@@ -741,14 +741,19 @@ class Unstreamable { @@ -741,14 +741,19 @@ class Unstreamable {
741 void set_x(int x) { x_ = x; } 741 void set_x(int x) { x_ = x; }
742 }; 742 };
743 743
  744 +// this needs to be a different check then the one after the function definition otherwise they conflict
  745 +static_assert(!CLI::detail::is_istreamable<Unstreamable, std::istream>::value, "Unstreamable type is streamable");
  746 +
744 std::istream &operator>>(std::istream &in, Unstreamable &value) { 747 std::istream &operator>>(std::istream &in, Unstreamable &value) {
745 int x; 748 int x;
746 in >> x; 749 in >> x;
747 value.set_x(x); 750 value.set_x(x);
748 return in; 751 return in;
749 } 752 }
  753 +// these need to be different classes otherwise the definitions conflict
  754 +static_assert(CLI::detail::is_istreamable<Unstreamable>::value, "Unstreamable type is still unstreamable");
750 755
751 -TEST_F(TApp, MakeUnstreamableOptiions) { 756 +TEST_F(TApp, MakeUnstreamableOptions) {
752 Unstreamable value; 757 Unstreamable value;
753 app.add_option("--value", value); 758 app.add_option("--value", value);
754 759
@@ -760,4 +765,13 @@ TEST_F(TApp, MakeUnstreamableOptiions) { @@ -760,4 +765,13 @@ TEST_F(TApp, MakeUnstreamableOptiions) {
760 765
761 // This used to fail to build, since it tries to stream from Unstreamable 766 // This used to fail to build, since it tries to stream from Unstreamable
762 app.add_option("--values2", values, "", false); 767 app.add_option("--values2", values, "", false);
  768 +
  769 + args = {"--value", "45"};
  770 + run();
  771 + EXPECT_EQ(value.get_x(), 45);
  772 +
  773 + args = {"--values", "45", "27", "34"};
  774 + run();
  775 + EXPECT_EQ(values.size(), 3u);
  776 + EXPECT_EQ(values[2].get_x(), 34);
763 } 777 }
tests/HelpersTest.cpp
@@ -855,6 +855,10 @@ TEST(Types, LexicalCastParsable) { @@ -855,6 +855,10 @@ TEST(Types, LexicalCastParsable) {
855 EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble 855 EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble
856 EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const 856 EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const
857 857
  858 + EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output));
  859 + EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble
  860 + EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + c++4.8 due to missing const
  861 +
858 EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); 862 EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
859 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); 863 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
860 } 864 }
tests/NewParseTest.cpp
@@ -137,7 +137,8 @@ namespace CLI { @@ -137,7 +137,8 @@ namespace CLI {
137 namespace detail { 137 namespace detail {
138 138
139 template <> 139 template <>
140 -bool lexical_cast<std::pair<std::string, std::string>>(std::string input, std::pair<std::string, std::string> &output) { 140 +bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,
  141 + std::pair<std::string, std::string> &output) {
141 142
142 auto sep = input.find_first_of(':'); 143 auto sep = input.find_first_of(':');
143 if((sep == std::string::npos) && (sep > 0)) { 144 if((sep == std::string::npos) && (sep > 0)) {
@@ -187,7 +188,7 @@ namespace detail { @@ -187,7 +188,7 @@ namespace detail {
187 188
188 // On MSVC and possibly some other new compilers this can be a free standing function without the template 189 // On MSVC and possibly some other new compilers this can be a free standing function without the template
189 // specialization but this is compiler dependent 190 // specialization but this is compiler dependent
190 -template <> bool lexical_cast<std::complex<double>>(std::string input, std::complex<double> &output) { 191 +template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
191 // regular expression to handle complex numbers of various formats 192 // regular expression to handle complex numbers of various formats
192 static const std::regex creg( 193 static const std::regex creg(
193 R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); 194 R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
@@ -209,8 +210,9 @@ template &lt;&gt; bool lexical_cast&lt;std::complex&lt;double&gt;&gt;(std::string input, std::comp @@ -209,8 +210,9 @@ template &lt;&gt; bool lexical_cast&lt;std::complex&lt;double&gt;&gt;(std::string input, std::comp
209 CLI::detail::trim(strval); 210 CLI::detail::trim(strval);
210 worked = CLI::detail::lexical_cast(strval, y); 211 worked = CLI::detail::lexical_cast(strval, y);
211 } else { 212 } else {
212 - CLI::detail::trim(input);  
213 - worked = CLI::detail::lexical_cast(input, x); 213 + std::string ival = input;
  214 + CLI::detail::trim(ival);
  215 + worked = CLI::detail::lexical_cast(ival, x);
214 } 216 }
215 } 217 }
216 if(worked) { 218 if(worked) {
@@ -259,3 +261,187 @@ TEST_F(TApp, AddingComplexParserDetail) { @@ -259,3 +261,187 @@ TEST_F(TApp, AddingComplexParserDetail) {
259 } 261 }
260 } 262 }
261 #endif 263 #endif
  264 +
  265 +/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
  266 +/// option assignments
  267 +template <class X> class objWrapper {
  268 + public:
  269 + objWrapper() = default;
  270 + explicit objWrapper(X obj) : val_{obj} {};
  271 + objWrapper(const objWrapper &ow) = default;
  272 + template <class TT> objWrapper(const TT &obj) = delete;
  273 + objWrapper &operator=(const objWrapper &) = default;
  274 + objWrapper &operator=(objWrapper &&) = default;
  275 + // delete all other assignment operators
  276 + template <typename TT> void operator=(TT &&obj) = delete;
  277 +
  278 + const X &value() const { return val_; }
  279 +
  280 + private:
  281 + X val_;
  282 +};
  283 +
  284 +// I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions
  285 +// so this test will not compile in that compiler
  286 +#if !defined(_MSC_VER) || _MSC_VER >= 1910
  287 +
  288 +static_assert(CLI::detail::is_direct_constructible<objWrapper<std::string>, std::string>::value,
  289 + "string wrapper isn't properly constructible");
  290 +
  291 +static_assert(!std::is_assignable<objWrapper<std::string>, std::string>::value,
  292 + "string wrapper is improperly assignable");
  293 +TEST_F(TApp, stringWrapper) {
  294 + objWrapper<std::string> sWrapper;
  295 + app.add_option("-v", sWrapper);
  296 + args = {"-v", "string test"};
  297 +
  298 + run();
  299 +
  300 + EXPECT_EQ(sWrapper.value(), "string test");
  301 +}
  302 +
  303 +static_assert(CLI::detail::is_direct_constructible<objWrapper<double>, double>::value,
  304 + "double wrapper isn't properly assignable");
  305 +
  306 +static_assert(!CLI::detail::is_direct_constructible<objWrapper<double>, int>::value,
  307 + "double wrapper can be assigned from int");
  308 +
  309 +static_assert(!CLI::detail::is_istreamable<objWrapper<double>>::value,
  310 + "double wrapper is input streamable and it shouldn't be");
  311 +
  312 +TEST_F(TApp, doubleWrapper) {
  313 + objWrapper<double> dWrapper;
  314 + app.add_option("-v", dWrapper);
  315 + args = {"-v", "2.36"};
  316 +
  317 + run();
  318 +
  319 + EXPECT_EQ(dWrapper.value(), 2.36);
  320 +
  321 + args = {"-v", "thing"};
  322 +
  323 + EXPECT_THROW(run(), CLI::ConversionError);
  324 +}
  325 +
  326 +static_assert(CLI::detail::is_direct_constructible<objWrapper<int>, int>::value,
  327 + "int wrapper is not constructible from int64");
  328 +
  329 +static_assert(!CLI::detail::is_direct_constructible<objWrapper<int>, double>::value,
  330 + "int wrapper is constructible from double");
  331 +
  332 +static_assert(!CLI::detail::is_istreamable<objWrapper<int>>::value,
  333 + "int wrapper is input streamable and it shouldn't be");
  334 +
  335 +TEST_F(TApp, intWrapper) {
  336 + objWrapper<int> iWrapper;
  337 + app.add_option("-v", iWrapper);
  338 + args = {"-v", "45"};
  339 +
  340 + run();
  341 +
  342 + EXPECT_EQ(iWrapper.value(), 45);
  343 + args = {"-v", "thing"};
  344 +
  345 + EXPECT_THROW(run(), CLI::ConversionError);
  346 +}
  347 +
  348 +static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, int>::value,
  349 + "float wrapper is constructible from int");
  350 +static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, double>::value,
  351 + "float wrapper is constructible from double");
  352 +
  353 +static_assert(!CLI::detail::is_istreamable<objWrapper<float>>::value,
  354 + "float wrapper is input streamable and it shouldn't be");
  355 +
  356 +TEST_F(TApp, floatWrapper) {
  357 + objWrapper<float> iWrapper;
  358 + app.add_option<objWrapper<float>, float>("-v", iWrapper);
  359 + args = {"-v", "45.3"};
  360 +
  361 + run();
  362 +
  363 + EXPECT_EQ(iWrapper.value(), 45.3f);
  364 + args = {"-v", "thing"};
  365 +
  366 + EXPECT_THROW(run(), CLI::ConversionError);
  367 +}
  368 +
  369 +#endif
  370 +/// simple class to wrap another with a very specific type constructor to test out some of the option assignments
  371 +class dobjWrapper {
  372 + public:
  373 + dobjWrapper() = default;
  374 + explicit dobjWrapper(double obj) : dval_{obj} {};
  375 + explicit dobjWrapper(int obj) : ival_{obj} {};
  376 +
  377 + double dvalue() const { return dval_; }
  378 + int ivalue() const { return ival_; }
  379 +
  380 + private:
  381 + double dval_{0.0};
  382 + int ival_{0};
  383 +};
  384 +
  385 +TEST_F(TApp, dobjWrapper) {
  386 + dobjWrapper iWrapper;
  387 + app.add_option("-v", iWrapper);
  388 + args = {"-v", "45"};
  389 +
  390 + run();
  391 +
  392 + EXPECT_EQ(iWrapper.ivalue(), 45);
  393 + EXPECT_EQ(iWrapper.dvalue(), 0.0);
  394 +
  395 + args = {"-v", "thing"};
  396 +
  397 + EXPECT_THROW(run(), CLI::ConversionError);
  398 + iWrapper = dobjWrapper{};
  399 +
  400 + args = {"-v", "45.1"};
  401 +
  402 + run();
  403 + EXPECT_EQ(iWrapper.ivalue(), 0);
  404 + EXPECT_EQ(iWrapper.dvalue(), 45.1);
  405 +}
  406 +
  407 +/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
  408 +/// option assignments
  409 +template <class X> class AobjWrapper {
  410 + public:
  411 + AobjWrapper() = default;
  412 + // delete all other constructors
  413 + template <class TT> AobjWrapper(TT &&obj) = delete;
  414 + // single assignment operator
  415 + void operator=(X val) { val_ = val; }
  416 + // delete all other assignment operators
  417 + template <typename TT> void operator=(TT &&obj) = delete;
  418 +
  419 + const X &value() const { return val_; }
  420 +
  421 + private:
  422 + X val_;
  423 +};
  424 +
  425 +static_assert(std::is_assignable<AobjWrapper<uint16_t> &, uint16_t>::value,
  426 + "AobjWrapper not assignable like it should be ");
  427 +
  428 +TEST_F(TApp, uint16Wrapper) {
  429 + AobjWrapper<uint16_t> sWrapper;
  430 + app.add_option<AobjWrapper<uint16_t>, uint16_t>("-v", sWrapper);
  431 + args = {"-v", "9"};
  432 +
  433 + run();
  434 +
  435 + EXPECT_EQ(sWrapper.value(), 9u);
  436 + args = {"-v", "thing"};
  437 +
  438 + EXPECT_THROW(run(), CLI::ConversionError);
  439 +
  440 + args = {"-v", "72456245754"};
  441 +
  442 + EXPECT_THROW(run(), CLI::ConversionError);
  443 +
  444 + args = {"-v", "-3"};
  445 +
  446 + EXPECT_THROW(run(), CLI::ConversionError);
  447 +}
tests/OptionalTest.cpp
@@ -3,6 +3,48 @@ @@ -3,6 +3,48 @@
3 3
4 #include "app_helper.hpp" 4 #include "app_helper.hpp"
5 5
  6 +// You can explicitly enable or disable support
  7 +// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too.
  8 +// We nest the check for __has_include and it's usage
  9 +#ifndef CLI11_STD_OPTIONAL
  10 +#ifdef __has_include
  11 +#if defined(CLI11_CPP17) && __has_include(<optional>)
  12 +#define CLI11_STD_OPTIONAL 1
  13 +#else
  14 +#define CLI11_STD_OPTIONAL 0
  15 +#endif
  16 +#else
  17 +#define CLI11_STD_OPTIONAL 0
  18 +#endif
  19 +#endif
  20 +
  21 +#ifndef CLI11_EXPERIMENTAL_OPTIONAL
  22 +#define CLI11_EXPERIMENTAL_OPTIONAL 0
  23 +#endif
  24 +
  25 +#ifndef CLI11_BOOST_OPTIONAL
  26 +#define CLI11_BOOST_OPTIONAL 0
  27 +#endif
  28 +
  29 +#if CLI11_BOOST_OPTIONAL
  30 +#include <boost/version.hpp>
  31 +#if BOOST_VERSION < 106100
  32 +#error "This boost::optional version is not supported, use 1.61 or better"
  33 +#endif
  34 +#endif
  35 +
  36 +#if CLI11_STD_OPTIONAL
  37 +#include <optional>
  38 +#endif
  39 +#if CLI11_EXPERIMENTAL_OPTIONAL
  40 +#include <experimental/optional>
  41 +#endif
  42 +#if CLI11_BOOST_OPTIONAL
  43 +#include <boost/optional.hpp>
  44 +#include <boost/optional/optional_io.hpp>
  45 +#endif
  46 +// [CLI11:verbatim]
  47 +
6 #if CLI11_STD_OPTIONAL 48 #if CLI11_STD_OPTIONAL
7 49
8 TEST_F(TApp, StdOptionalTest) { 50 TEST_F(TApp, StdOptionalTest) {
@@ -55,13 +97,69 @@ TEST_F(TApp, BoostOptionalTest) { @@ -55,13 +97,69 @@ TEST_F(TApp, BoostOptionalTest) {
55 run(); 97 run();
56 EXPECT_TRUE(opt); 98 EXPECT_TRUE(opt);
57 EXPECT_EQ(*opt, 1); 99 EXPECT_EQ(*opt, 1);
  100 + opt = {};
  101 + args = {"--count", "3"};
  102 + run();
  103 + EXPECT_TRUE(opt);
  104 + EXPECT_EQ(*opt, 3);
  105 +}
58 106
  107 +TEST_F(TApp, BoostOptionalint64Test) {
  108 + boost::optional<int64_t> opt;
  109 + app.add_option("-c,--count", opt);
  110 + run();
  111 + EXPECT_FALSE(opt);
  112 +
  113 + args = {"-c", "1"};
  114 + run();
  115 + EXPECT_TRUE(opt);
  116 + EXPECT_EQ(*opt, 1);
  117 + opt = {};
59 args = {"--count", "3"}; 118 args = {"--count", "3"};
60 run(); 119 run();
61 EXPECT_TRUE(opt); 120 EXPECT_TRUE(opt);
62 EXPECT_EQ(*opt, 3); 121 EXPECT_EQ(*opt, 3);
63 } 122 }
64 123
  124 +TEST_F(TApp, BoostOptionalStringTest) {
  125 + boost::optional<std::string> opt;
  126 + app.add_option("-s,--string", opt);
  127 + run();
  128 + EXPECT_FALSE(opt);
  129 +
  130 + args = {"-s", "strval"};
  131 + run();
  132 + EXPECT_TRUE(opt);
  133 + EXPECT_EQ(*opt, "strval");
  134 + opt = {};
  135 + args = {"--string", "strv"};
  136 + run();
  137 + EXPECT_TRUE(opt);
  138 + EXPECT_EQ(*opt, "strv");
  139 +}
  140 +
  141 +TEST_F(TApp, BoostOptionalEnumTest) {
  142 + enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 };
  143 + boost::optional<eval> opt;
  144 + auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt);
  145 + optptr->capture_default_str();
  146 +
  147 + auto dstring = optptr->get_default_str();
  148 + EXPECT_TRUE(dstring.empty());
  149 + run();
  150 + EXPECT_FALSE(opt);
  151 +
  152 + args = {"-v", "3"};
  153 + run();
  154 + EXPECT_TRUE(opt);
  155 + EXPECT_TRUE(*opt == eval::val3);
  156 + opt = {};
  157 + args = {"--val", "1"};
  158 + run();
  159 + EXPECT_TRUE(opt);
  160 + EXPECT_TRUE(*opt == eval::val1);
  161 +}
  162 +
65 TEST_F(TApp, BoostOptionalVector) { 163 TEST_F(TApp, BoostOptionalVector) {
66 boost::optional<std::vector<int>> opt; 164 boost::optional<std::vector<int>> opt;
67 app.add_option_function<std::vector<int>>("-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector") 165 app.add_option_function<std::vector<int>>("-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector")
tests/TransformTest.cpp
@@ -2,6 +2,15 @@ @@ -2,6 +2,15 @@
2 2
3 #include <unordered_map> 3 #include <unordered_map>
4 4
  5 +#if defined(CLI11_CPP17)
  6 +#if defined(__has_include)
  7 +#if __has_include(<string_view>)
  8 +#include <string_view>
  9 +#define CLI11_HAS_STRING_VIEW
  10 +#endif
  11 +#endif
  12 +#endif
  13 +
5 TEST_F(TApp, SimpleTransform) { 14 TEST_F(TApp, SimpleTransform) {
6 int value; 15 int value;
7 auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}})); 16 auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
@@ -112,6 +121,7 @@ TEST_F(TApp, StringViewTransformFn) { @@ -112,6 +121,7 @@ TEST_F(TApp, StringViewTransformFn) {
112 run(); 121 run();
113 EXPECT_EQ(value, "mapped"); 122 EXPECT_EQ(value, "mapped");
114 } 123 }
  124 +
115 #endif 125 #endif
116 126
117 TEST_F(TApp, SimpleNumericalTransformFn) { 127 TEST_F(TApp, SimpleNumericalTransformFn) {