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 3 branches:
2 4 only:
3 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 55 - [Contribute](#contribute)
56 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 60 ## Background
61 61  
... ... @@ -194,21 +194,25 @@ 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
  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 198 help_string="")
199 199  
200 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 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.
  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 210 // Add flags
207 211 app.add_flag(option_name,
208 212 help_string="")
209 213  
210 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 216 help_string="")
213 217  
214 218 app.add_flag_function(option_name, // πŸ†•
... ... @@ -240,6 +244,25 @@ An option name must start with a alphabetic character, underscore, a number πŸ†•
240 244  
241 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 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 268 ```cpp
... ... @@ -263,8 +286,6 @@ using any of those flags on the command line will result in the specified number
263 286  
264 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 289 #### Example
269 290  
270 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 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 322 - `->configurable(false)`: Disable this option from being in a configuration file.
302 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 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 4 // file LICENSE or https://github.com/CLIUtils/CLI11 for details.
5 5  
6 6 #include <algorithm>
7   -#include <deque>
8 7 #include <functional>
9 8 #include <iostream>
10 9 #include <iterator>
11 10 #include <memory>
12 11 #include <numeric>
13   -#include <set>
14 12 #include <sstream>
15 13 #include <string>
16 14 #include <utility>
... ... @@ -469,18 +467,23 @@ class App {
469 467 }
470 468  
471 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 474 Option *add_option(std::string option_name,
474 475 T &variable, ///< The variable to set
475 476 std::string option_description = "",
476 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 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 488 return opt;
486 489 }
... ...
include/CLI/CLI.hpp
... ... @@ -10,8 +10,6 @@
10 10  
11 11 #include "CLI/Macros.hpp"
12 12  
13   -#include "CLI/Optional.hpp"
14   -
15 13 #include "CLI/StringTools.hpp"
16 14  
17 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 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 28 } // namespace enums
37 29  
38 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 49 }
58 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 53 /// Simple function to join a string
73 54 template <typename T> std::string join(const T &v, std::string delim = ",") {
... ...
include/CLI/TypeTools.hpp
... ... @@ -10,17 +10,6 @@
10 10 #include <type_traits>
11 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 13 namespace CLI {
25 14  
26 15 // Type tools
... ... @@ -83,10 +72,6 @@ template &lt;typename T&gt; struct IsMemberType { using type = T; };
83 72 /// The main custom type needed here is const char * should be a string.
84 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 75 namespace detail {
91 76  
92 77 // These are utilities for IsMember
... ... @@ -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 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 156 static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
148 157  
149 158 template <typename, typename> static auto test(...) -> std::false_type;
150 159  
151 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 189 /// Convert an object to a string (directly forward if this can become a string)
156 190 template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
157 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 194  
161 195 /// Convert an object to a string (streaming must be supported for that type)
162 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 199 std::string to_string(T &&value) {
166 200 std::stringstream stream;
167 201 stream << value;
... ... @@ -170,12 +204,30 @@ std::string to_string(T &amp;&amp;value) {
170 204  
171 205 /// If conversion is not supported, return an empty string (streaming is not supported for that type)
172 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 209 std::string to_string(T &&) {
176 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 231 // Type name print
180 232  
181 233 /// Was going to be based on
... ... @@ -277,7 +329,7 @@ template &lt;
277 329 typename T,
278 330 enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value,
279 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 333 try {
282 334 size_t n = 0;
283 335 long long output_ll = std::stoll(input, &n, 0);
... ... @@ -294,7 +346,7 @@ bool lexical_cast(std::string input, T &amp;output) {
294 346 template <typename T,
295 347 enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> =
296 348 detail::dummy>
297   -bool lexical_cast(std::string input, T &output) {
  349 +bool lexical_cast(const std::string &input, T &output) {
298 350 if(!input.empty() && input.front() == '-')
299 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 364  
313 365 /// Boolean values
314 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 368 try {
317 369 auto out = to_flag_value(input);
318 370 output = (out > 0);
... ... @@ -324,7 +376,7 @@ bool lexical_cast(std::string input, T &amp;output) {
324 376  
325 377 /// Floats
326 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 380 try {
329 381 size_t n = 0;
330 382 output = static_cast<T>(std::stold(input, &n));
... ... @@ -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 392 template <typename T,
341 393 enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
342 394 std::is_assignable<T &, std::string>::value,
343 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 397 output = input;
346 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 411 /// Enumerations
350 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 414 typename std::underlying_type<T>::type val;
353 415 bool retval = detail::lexical_cast(input, val);
354 416 if(!retval) {
... ... @@ -358,21 +420,112 @@ bool lexical_cast(std::string input, T &amp;output) {
358 420 return true;
359 421 }
360 422  
361   -/// Non-string parsable
  423 +/// Assignable from double or int
362 424 template <typename T,
363 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 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 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 529 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
377 530 /// common true and false strings then uses stoll to convert the rest for summing
378 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 539 }
387 540  
388 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 544 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
391 545 /// common true and false strings then uses stoll to convert the rest for summing
392 546 template <typename T,
... ...
include/CLI/Validators.hpp
... ... @@ -407,11 +407,11 @@ class Bound : public Validator {
407 407 return "Value " + input + " could not be converted";
408 408 }
409 409 if(val < min)
410   - input = detail::as_string(min);
  410 + input = detail::to_string(min);
411 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 451 std::string out(1, '{');
452 452 out.append(detail::join(detail::smart_deref(map),
453 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 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 460 return res;
459 461 },
... ... @@ -581,7 +583,7 @@ class IsMember : public Validator {
581 583 if(res.first) {
582 584 // Make sure the version in the input string is identical to the one in the set
583 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 589 // Return empty error string (success)
... ... @@ -649,7 +651,7 @@ class Transformer : public Validator {
649 651 }
650 652 auto res = detail::search(mapping, b, filter_fn);
651 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 656 return std::string{};
655 657 };
... ... @@ -699,7 +701,7 @@ class CheckedTransformer : public Validator {
699 701 out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
700 702 out += detail::join(
701 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 706 out.push_back('}');
705 707 return out;
... ... @@ -716,12 +718,12 @@ class CheckedTransformer : public Validator {
716 718 }
717 719 auto res = detail::search(mapping, b, filter_fn);
718 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 722 return std::string{};
721 723 }
722 724 }
723 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 727 if(output_string == input) {
726 728 return std::string();
727 729 }
... ... @@ -832,10 +834,10 @@ class AsNumberWithUnit : public Validator {
832 834 // perform safe multiplication
833 835 bool ok = detail::checked_multiply(num, it->second);
834 836 if(!ok) {
835   - throw ValidationError(detail::as_string(num) + " multiplied by " + unit +
  837 + throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
836 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 842 return {};
841 843 };
... ...
tests/AppTest.cpp
... ... @@ -669,6 +669,27 @@ TEST_F(TApp, ShortOpts) {
669 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 693 TEST_F(TApp, DefaultOpts) {
673 694  
674 695 int i = 3;
... ...
tests/CreationTest.cpp
... ... @@ -741,14 +741,19 @@ class Unstreamable {
741 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 747 std::istream &operator>>(std::istream &in, Unstreamable &value) {
745 748 int x;
746 749 in >> x;
747 750 value.set_x(x);
748 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 757 Unstreamable value;
753 758 app.add_option("--value", value);
754 759  
... ... @@ -760,4 +765,13 @@ TEST_F(TApp, MakeUnstreamableOptiions) {
760 765  
761 766 // This used to fail to build, since it tries to stream from Unstreamable
762 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 855 EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble
856 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 862 EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
859 863 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
860 864 }
... ...
tests/NewParseTest.cpp
... ... @@ -137,7 +137,8 @@ namespace CLI {
137 137 namespace detail {
138 138  
139 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 143 auto sep = input.find_first_of(':');
143 144 if((sep == std::string::npos) && (sep > 0)) {
... ... @@ -187,7 +188,7 @@ namespace detail {
187 188  
188 189 // On MSVC and possibly some other new compilers this can be a free standing function without the template
189 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 192 // regular expression to handle complex numbers of various formats
192 193 static const std::regex creg(
193 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 210 CLI::detail::trim(strval);
210 211 worked = CLI::detail::lexical_cast(strval, y);
211 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 218 if(worked) {
... ... @@ -259,3 +261,187 @@ TEST_F(TApp, AddingComplexParserDetail) {
259 261 }
260 262 }
261 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 3  
4 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 48 #if CLI11_STD_OPTIONAL
7 49  
8 50 TEST_F(TApp, StdOptionalTest) {
... ... @@ -55,13 +97,69 @@ TEST_F(TApp, BoostOptionalTest) {
55 97 run();
56 98 EXPECT_TRUE(opt);
57 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 118 args = {"--count", "3"};
60 119 run();
61 120 EXPECT_TRUE(opt);
62 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 163 TEST_F(TApp, BoostOptionalVector) {
66 164 boost::optional<std::vector<int>> opt;
67 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 2  
3 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 14 TEST_F(TApp, SimpleTransform) {
6 15 int value;
7 16 auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
... ... @@ -112,6 +121,7 @@ TEST_F(TApp, StringViewTransformFn) {
112 121 run();
113 122 EXPECT_EQ(value, "mapped");
114 123 }
  124 +
115 125 #endif
116 126  
117 127 TEST_F(TApp, SimpleNumericalTransformFn) {
... ...