1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-03 14:03:52 +00:00

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
This commit is contained in:
Philip Top 2019-07-28 21:19:35 -07:00 committed by Henry Schreiner
parent dbd4933506
commit eab92ed988
14 changed files with 586 additions and 189 deletions

View File

@ -1,3 +1,5 @@
version: 1.8.0.{build}
branches: branches:
only: only:
- master - master

View File

@ -55,7 +55,7 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
- [Contribute](#contribute) - [Contribute](#contribute)
- [License](#license) - [License](#license)
Features that were added in the last released major version are marked with "🆕". Features only available in master are marked with "🆕". Features that were added in the last released major version are marked with "🆕". Features only available in master are marked with "🚧".
## Background ## Background
@ -194,21 +194,25 @@ While all options internally are the same type, there are several ways to add an
app.add_option(option_name, help_str="") // 🆕 app.add_option(option_name, help_str="") // 🆕
app.add_option(option_name, app.add_option(option_name,
variable_to_bind_to, // bool, int, float, vector, 🆕 enum, or string-like, or anything with a defined conversion from a string 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.
help_string="") help_string="")
app.add_option_function<type>(option_name, app.add_option_function<type>(option_name,
function <void(const type &value)>, // 🆕 int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string 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.
help_string="") help_string="")
app.add_complex(... // Special case: support for complex numbers app.add_complex(... // Special case: support for complex numbers
//🚧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.
app.add_option<typename T, typename XC>(option_name,
T &output, // output must be assignable or constructible from a value of type XC
help_string="")
// Add flags // Add flags
app.add_flag(option_name, app.add_flag(option_name,
help_string="") help_string="")
app.add_flag(option_name, app.add_flag(option_name,
variable_to_bind_to, // bool, int, 🆕 float, 🆕 vector, 🆕 enum, or 🆕 string-like, or 🆕 anything with a defined conversion from a string variable_to_bind_to, // bool, int, 🆕 float, 🆕 vector, 🆕 enum, or 🆕 string-like, or 🆕 anything with a defined conversion from a string like add_option
help_string="") help_string="")
app.add_flag_function(option_name, // 🆕 app.add_flag_function(option_name, // 🆕
@ -240,6 +244,25 @@ An option name must start with a alphabetic character, underscore, a number 🆕
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. 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.
🚧 The two parameter template overload can be used in cases where you want to restrict the input such as
```
double val
app.add_option<double,unsigned int>("-v",val);
```
which would first verify the input is convertible to an int before assigning it. Or using some variant type
```
using vtype=std::variant<int, double, std::string>;
vtype v1;
app.add_option<vtype,std:string>("--vs",v1);
app.add_option<vtype,int>("--vi",v1);
app.add_option<vtype,double>("--vf",v1);
```
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.
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.
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.
🆕 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: 🆕 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:
```cpp ```cpp
@ -263,8 +286,6 @@ using any of those flags on the command line will result in the specified number
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. 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.
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.
#### Example #### Example
- `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` - `"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:
- `->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. - `->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.
- `->configurable(false)`: Disable this option from being in a configuration file. - `->configurable(false)`: Disable this option from being in a configuration file.
`->capture_default_str()`: 🆕 Store the current value attached and display it in the help string. `->capture_default_str()`: 🆕 Store the current value attached and display it in the help string.
`->default_function(std::string())`: 🆕 Advanced: Change the function that `capture_default_str()` uses. - `->default_function(std::string())`: 🆕 Advanced: Change the function that `capture_default_str()` uses.
`->always_capture_default()`: 🆕 Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. - `->always_capture_default()`: 🆕 Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
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. 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.

View File

@ -4,13 +4,11 @@
// file LICENSE or https://github.com/CLIUtils/CLI11 for details. // file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <algorithm> #include <algorithm>
#include <deque>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
#include <memory> #include <memory>
#include <numeric> #include <numeric>
#include <set>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <utility> #include <utility>
@ -469,18 +467,23 @@ class App {
} }
/// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy>
template <typename T,
typename XC = T,
enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy>
Option *add_option(std::string option_name, Option *add_option(std::string option_name,
T &variable, ///< The variable to set T &variable, ///< The variable to set
std::string option_description = "", std::string option_description = "",
bool defaulted = false) { bool defaulted = false) {
auto fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; auto fun = [&variable](CLI::results_t res) { // comment for spacing
return detail::lexical_assign<T, XC>(res[0], variable);
};
Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
return std::string(CLI::detail::to_string(variable)); return std::string(CLI::detail::checked_to_string<T, XC>(variable));
}); });
opt->type_name(detail::type_name<T>()); opt->type_name(detail::type_name<XC>());
return opt; return opt;
} }

View File

@ -10,8 +10,6 @@
#include "CLI/Macros.hpp" #include "CLI/Macros.hpp"
#include "CLI/Optional.hpp"
#include "CLI/StringTools.hpp" #include "CLI/StringTools.hpp"
#include "CLI/Error.hpp" #include "CLI/Error.hpp"

View File

@ -1,97 +0,0 @@
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <istream>
#include "CLI/Macros.hpp"
// [CLI11:verbatim]
// You can explicitly enable or disable support
// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too.
// We nest the check for __has_include and it's usage
#ifndef CLI11_STD_OPTIONAL
#ifdef __has_include
#if defined(CLI11_CPP17) && __has_include(<optional>)
#define CLI11_STD_OPTIONAL 1
#else
#define CLI11_STD_OPTIONAL 0
#endif
#else
#define CLI11_STD_OPTIONAL 0
#endif
#endif
#ifndef CLI11_EXPERIMENTAL_OPTIONAL
#define CLI11_EXPERIMENTAL_OPTIONAL 0
#endif
#ifndef CLI11_BOOST_OPTIONAL
#define CLI11_BOOST_OPTIONAL 0
#endif
#if CLI11_BOOST_OPTIONAL
#include <boost/version.hpp>
#if BOOST_VERSION < 106100
#error "This boost::optional version is not supported, use 1.61 or better"
#endif
#endif
#if CLI11_STD_OPTIONAL
#include <optional>
#endif
#if CLI11_EXPERIMENTAL_OPTIONAL
#include <experimental/optional>
#endif
#if CLI11_BOOST_OPTIONAL
#include <boost/optional.hpp>
#include <boost/optional/optional_io.hpp>
#endif
// [CLI11:verbatim]
namespace CLI {
#if CLI11_STD_OPTIONAL
template <typename T> std::istream &operator>>(std::istream &in, std::optional<T> &val) {
T v;
in >> v;
val = v;
return in;
}
#endif
#if CLI11_EXPERIMENTAL_OPTIONAL
template <typename T> std::istream &operator>>(std::istream &in, std::experimental::optional<T> &val) {
T v;
in >> v;
val = v;
return in;
}
#endif
#if CLI11_BOOST_OPTIONAL
template <typename T> std::istream &operator>>(std::istream &in, boost::optional<T> &val) {
T v;
in >> v;
val = v;
return in;
}
#endif
// Export the best optional to the CLI namespace
#if CLI11_STD_OPTIONAL
using std::optional;
#elif CLI11_EXPERIMENTAL_OPTIONAL
using std::experimental::optional;
#elif CLI11_BOOST_OPTIONAL
using boost::optional;
#endif
// This is true if any optional is found
#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL
#define CLI11_OPTIONAL 1
#endif
} // namespace CLI

View File

@ -25,14 +25,6 @@ std::ostream &operator<<(std::ostream &in, const T &item) {
return in << static_cast<typename std::underlying_type<T>::type>(item); return in << static_cast<typename std::underlying_type<T>::type>(item);
} }
/// input streaming for enumerations
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
std::istream &operator>>(std::istream &in, T &item) {
typename std::underlying_type<T>::type i;
in >> i;
item = static_cast<T>(i);
return in;
}
} // namespace enums } // namespace enums
/// Export to CLI namespace /// Export to CLI namespace
@ -57,17 +49,6 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
} }
return elems; return elems;
} }
/// simple utility to convert various types to a string
template <typename T> inline std::string as_string(const T &v) {
std::ostringstream s;
s << v;
return s.str();
}
// if the data type is already a string just forward it
template <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type>
inline auto as_string(T &&v) -> decltype(std::forward<T>(v)) {
return std::forward<T>(v);
}
/// Simple function to join a string /// Simple function to join a string
template <typename T> std::string join(const T &v, std::string delim = ",") { template <typename T> std::string join(const T &v, std::string delim = ",") {

View File

@ -10,17 +10,6 @@
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
// [CLI11:verbatim]
#if defined(CLI11_CPP17)
#if defined(__has_include)
#if __has_include(<string_view>)
#include <string_view>
#define CLI11_HAS_STRING_VIEW
#endif
#endif
#endif
// [CLI11:verbatim]
namespace CLI { namespace CLI {
// Type tools // Type tools
@ -83,10 +72,6 @@ template <typename T> struct IsMemberType { using type = T; };
/// The main custom type needed here is const char * should be a string. /// The main custom type needed here is const char * should be a string.
template <> struct IsMemberType<const char *> { using type = std::string; }; template <> struct IsMemberType<const char *> { using type = std::string; };
#ifdef CLI11_HAS_STRING_VIEW
template <> struct IsMemberType<std::string_view> { using type = std::string; };
#endif
namespace detail { namespace detail {
// These are utilities for IsMember // These are utilities for IsMember
@ -139,19 +124,68 @@ struct pair_adaptor<
} }
}; };
// Check for streamability // Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning
// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in
// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a
// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out.
// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be
// suppressed
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnarrowing"
#endif
// check for constructibility from a specific type and copy assignable used in the parse detection
template <typename T, typename C> class is_direct_constructible {
template <typename TT, typename CC>
static auto test(int) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T, C>(0))::value;
};
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
// Check for output streamability
// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream // Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
template <typename S, typename T> class is_streamable { template <typename T, typename S = std::ostringstream> class is_ostreamable {
template <typename SS, typename TT> template <typename TT, typename SS>
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type()); static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
template <typename, typename> static auto test(...) -> std::false_type; template <typename, typename> static auto test(...) -> std::false_type;
public: public:
static const bool value = decltype(test<S, T>(0))::value; static const bool value = decltype(test<T, S>(0))::value;
}; };
/// Check for input streamability
template <typename T, typename S = std::istringstream> class is_istreamable {
template <typename TT, typename SS>
static auto test(int) -> decltype(std::declval<SS &>() >> std::declval<TT &>(), std::true_type());
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T, S>(0))::value;
};
/// Templated operation to get a value from a stream
template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
bool from_stream(const std::string &istring, T &obj) {
std::istringstream is;
is.str(istring);
is >> obj;
return !is.fail() && !is.rdbuf()->in_avail();
}
template <typename T, enable_if_t<!is_istreamable<T>::value, detail::enabler> = detail::dummy>
bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
return false;
}
/// Convert an object to a string (directly forward if this can become a string) /// Convert an object to a string (directly forward if this can become a string)
template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy> template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
auto to_string(T &&value) -> decltype(std::forward<T>(value)) { auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
@ -160,8 +194,8 @@ auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
/// Convert an object to a string (streaming must be supported for that type) /// Convert an object to a string (streaming must be supported for that type)
template <typename T, template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && is_streamable<std::stringstream, T>::value, enable_if_t<!std::is_constructible<std::string, T>::value && is_ostreamable<T>::value, detail::enabler> =
detail::enabler> = detail::dummy> detail::dummy>
std::string to_string(T &&value) { std::string to_string(T &&value) {
std::stringstream stream; std::stringstream stream;
stream << value; stream << value;
@ -170,12 +204,30 @@ std::string to_string(T &&value) {
/// If conversion is not supported, return an empty string (streaming is not supported for that type) /// If conversion is not supported, return an empty string (streaming is not supported for that type)
template <typename T, template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_streamable<std::stringstream, T>::value, enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value, detail::enabler> =
detail::enabler> = detail::dummy> detail::dummy>
std::string to_string(T &&) { std::string to_string(T &&) {
return std::string{}; return std::string{};
} }
/// special template overload
template <typename T1,
typename T2,
typename T,
enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) {
return to_string(std::forward<T>(value));
}
/// special template overload
template <typename T1,
typename T2,
typename T,
enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
std::string checked_to_string(T &&) {
return std::string{};
}
// Type name print // Type name print
/// Was going to be based on /// Was going to be based on
@ -277,7 +329,7 @@ template <
typename T, typename T,
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value, enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value,
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) { bool lexical_cast(const std::string &input, T &output) {
try { try {
size_t n = 0; size_t n = 0;
long long output_ll = std::stoll(input, &n, 0); long long output_ll = std::stoll(input, &n, 0);
@ -294,7 +346,7 @@ bool lexical_cast(std::string input, T &output) {
template <typename T, template <typename T,
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> = enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> =
detail::dummy> detail::dummy>
bool lexical_cast(std::string input, T &output) { bool lexical_cast(const std::string &input, T &output) {
if(!input.empty() && input.front() == '-') if(!input.empty() && input.front() == '-')
return false; // std::stoull happily converts negative values to junk without any errors. return false; // std::stoull happily converts negative values to junk without any errors.
@ -312,7 +364,7 @@ bool lexical_cast(std::string input, T &output) {
/// Boolean values /// Boolean values
template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) { bool lexical_cast(const std::string &input, T &output) {
try { try {
auto out = to_flag_value(input); auto out = to_flag_value(input);
output = (out > 0); output = (out > 0);
@ -324,7 +376,7 @@ bool lexical_cast(std::string input, T &output) {
/// Floats /// Floats
template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) { bool lexical_cast(const std::string &input, T &output) {
try { try {
size_t n = 0; size_t n = 0;
output = static_cast<T>(std::stold(input, &n)); output = static_cast<T>(std::stold(input, &n));
@ -336,19 +388,29 @@ bool lexical_cast(std::string input, T &output) {
} }
} }
/// String and similar /// String and similar direct assignment
template <typename T, template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
std::is_assignable<T &, std::string>::value, std::is_assignable<T &, std::string>::value,
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) { bool lexical_cast(const std::string &input, T &output) {
output = input; output = input;
return true; return true;
} }
/// String and similar constructible and copy assignment
template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value && std::is_constructible<T, std::string>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
output = T(input);
return true;
}
/// Enumerations /// Enumerations
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) { bool lexical_cast(const std::string &input, T &output) {
typename std::underlying_type<T>::type val; typename std::underlying_type<T>::type val;
bool retval = detail::lexical_cast(input, val); bool retval = detail::lexical_cast(input, val);
if(!retval) { if(!retval) {
@ -358,21 +420,112 @@ bool lexical_cast(std::string input, T &output) {
return true; return true;
} }
/// Non-string parsable /// Assignable from double or int
template <typename T, template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value && !std::is_enum<T>::value, !std::is_assignable<T &, std::string>::value &&
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value,
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) { bool lexical_cast(const std::string &input, T &output) {
std::istringstream is; int val;
if(lexical_cast(input, val)) {
output = T(val);
return true;
} else {
double dval;
if(lexical_cast(input, dval)) {
output = T{dval};
return true;
}
}
return from_stream(input, output);
}
is.str(input); /// Assignable from int64
is >> output; template <typename T,
return !is.fail() && !is.rdbuf()->in_avail(); enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value &&
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
!is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
int val;
if(lexical_cast(input, val)) {
output = T(val);
return true;
}
return from_stream(input, output);
}
/// Assignable from double
template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value &&
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
double val;
if(lexical_cast(input, val)) {
output = T{val};
return true;
}
return from_stream(input, output);
}
/// Non-string parsable by a stream
template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value &&
!std::is_constructible<T, std::string>::value && !std::is_enum<T>::value &&
!is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
static_assert(is_istreamable<T>::value,
"option object type must have a lexical cast overload or streaming input operator(>>) defined if it "
"is convertible from another type use the add_option<T, XC>(...) with XC being the known type");
return from_stream(input, output);
}
/// Assign a value through lexical cast operations
template <class T, class XC, enable_if_t<std::is_same<T, XC>::value, detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, T &output) {
return lexical_cast(input, output);
}
/// Assign a value converted from a string in lexical cast to the output value directly
template <
class T,
class XC,
enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, T &output) {
XC val;
auto parse_result = lexical_cast<XC>(input, val);
if(parse_result) {
output = val;
}
return parse_result;
}
/// Assign a value from a lexical cast through constructing a value and move assigning it
template <class T,
class XC,
enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value &&
std::is_move_assignable<T>::value,
detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, T &output) {
XC val;
bool parse_result = lexical_cast<XC>(input, val);
if(parse_result) {
output = T(val); // use () form of constructor to allow some implicit conversions
}
return parse_result;
} }
/// Sum a vector of flag representations /// Sum a vector of flag representations
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
/// by
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
/// common true and false strings then uses stoll to convert the rest for summing /// common true and false strings then uses stoll to convert the rest for summing
template <typename T, template <typename T,
@ -386,7 +539,8 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
} }
/// Sum a vector of flag representations /// Sum a vector of flag representations
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
/// by
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
/// common true and false strings then uses stoll to convert the rest for summing /// common true and false strings then uses stoll to convert the rest for summing
template <typename T, template <typename T,

View File

@ -407,11 +407,11 @@ class Bound : public Validator {
return "Value " + input + " could not be converted"; return "Value " + input + " could not be converted";
} }
if(val < min) if(val < min)
input = detail::as_string(min); input = detail::to_string(min);
else if(val > max) else if(val > max)
input = detail::as_string(max); input = detail::to_string(max);
return std::string(); return std::string{};
}; };
} }
@ -451,9 +451,11 @@ template <typename T> std::string generate_map(const T &map, bool key_only = fal
std::string out(1, '{'); std::string out(1, '{');
out.append(detail::join(detail::smart_deref(map), out.append(detail::join(detail::smart_deref(map),
[key_only](const iteration_type_t &v) { [key_only](const iteration_type_t &v) {
auto res = detail::as_string(detail::pair_adaptor<element_t>::first(v)); std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
if(!key_only) { if(!key_only) {
res += "->" + detail::as_string(detail::pair_adaptor<element_t>::second(v)); res.append("->");
res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
} }
return res; return res;
}, },
@ -581,7 +583,7 @@ class IsMember : public Validator {
if(res.first) { if(res.first) {
// Make sure the version in the input string is identical to the one in the set // Make sure the version in the input string is identical to the one in the set
if(filter_fn) { if(filter_fn) {
input = detail::as_string(detail::pair_adaptor<element_t>::first(*(res.second))); input = detail::to_string(detail::pair_adaptor<element_t>::first(*(res.second)));
} }
// Return empty error string (success) // Return empty error string (success)
@ -649,7 +651,7 @@ class Transformer : public Validator {
} }
auto res = detail::search(mapping, b, filter_fn); auto res = detail::search(mapping, b, filter_fn);
if(res.first) { if(res.first) {
input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second)); input = detail::to_string(detail::pair_adaptor<element_t>::second(*res.second));
} }
return std::string{}; return std::string{};
}; };
@ -699,7 +701,7 @@ class CheckedTransformer : public Validator {
out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
out += detail::join( out += detail::join(
detail::smart_deref(mapping), detail::smart_deref(mapping),
[](const iteration_type_t &v) { return detail::as_string(detail::pair_adaptor<element_t>::second(v)); }, [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
","); ",");
out.push_back('}'); out.push_back('}');
return out; return out;
@ -716,12 +718,12 @@ class CheckedTransformer : public Validator {
} }
auto res = detail::search(mapping, b, filter_fn); auto res = detail::search(mapping, b, filter_fn);
if(res.first) { if(res.first) {
input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second)); input = detail::to_string(detail::pair_adaptor<element_t>::second(*res.second));
return std::string{}; return std::string{};
} }
} }
for(const auto &v : detail::smart_deref(mapping)) { for(const auto &v : detail::smart_deref(mapping)) {
auto output_string = detail::as_string(detail::pair_adaptor<element_t>::second(v)); auto output_string = detail::to_string(detail::pair_adaptor<element_t>::second(v));
if(output_string == input) { if(output_string == input) {
return std::string(); return std::string();
} }
@ -832,10 +834,10 @@ class AsNumberWithUnit : public Validator {
// perform safe multiplication // perform safe multiplication
bool ok = detail::checked_multiply(num, it->second); bool ok = detail::checked_multiply(num, it->second);
if(!ok) { if(!ok) {
throw ValidationError(detail::as_string(num) + " multiplied by " + unit + throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
" factor would cause number overflow. Use smaller value."); " factor would cause number overflow. Use smaller value.");
} }
input = detail::as_string(num); input = detail::to_string(num);
return {}; return {};
}; };

View File

@ -669,6 +669,27 @@ TEST_F(TApp, ShortOpts) {
EXPECT_EQ(app.count_all(), 3u); EXPECT_EQ(app.count_all(), 3u);
} }
TEST_F(TApp, TwoParamTemplateOpts) {
double funnyint;
auto opt = app.add_option<double, unsigned int>("-y", funnyint);
args = {"-y", "32"};
run();
EXPECT_EQ(32.0, funnyint);
args = {"-y", "32.3"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"-y", "-19"};
EXPECT_THROW(run(), CLI::ConversionError);
opt->capture_default_str();
EXPECT_TRUE(opt->get_default_str().empty());
}
TEST_F(TApp, DefaultOpts) { TEST_F(TApp, DefaultOpts) {
int i = 3; int i = 3;

View File

@ -741,14 +741,19 @@ class Unstreamable {
void set_x(int x) { x_ = x; } void set_x(int x) { x_ = x; }
}; };
// this needs to be a different check then the one after the function definition otherwise they conflict
static_assert(!CLI::detail::is_istreamable<Unstreamable, std::istream>::value, "Unstreamable type is streamable");
std::istream &operator>>(std::istream &in, Unstreamable &value) { std::istream &operator>>(std::istream &in, Unstreamable &value) {
int x; int x;
in >> x; in >> x;
value.set_x(x); value.set_x(x);
return in; return in;
} }
// these need to be different classes otherwise the definitions conflict
static_assert(CLI::detail::is_istreamable<Unstreamable>::value, "Unstreamable type is still unstreamable");
TEST_F(TApp, MakeUnstreamableOptiions) { TEST_F(TApp, MakeUnstreamableOptions) {
Unstreamable value; Unstreamable value;
app.add_option("--value", value); app.add_option("--value", value);
@ -760,4 +765,13 @@ TEST_F(TApp, MakeUnstreamableOptiions) {
// This used to fail to build, since it tries to stream from Unstreamable // This used to fail to build, since it tries to stream from Unstreamable
app.add_option("--values2", values, "", false); app.add_option("--values2", values, "", false);
args = {"--value", "45"};
run();
EXPECT_EQ(value.get_x(), 45);
args = {"--values", "45", "27", "34"};
run();
EXPECT_EQ(values.size(), 3u);
EXPECT_EQ(values[2].get_x(), 34);
} }

View File

@ -855,6 +855,10 @@ TEST(Types, LexicalCastParsable) {
EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble
EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const
EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output));
EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble
EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + c++4.8 due to missing const
EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
} }

View File

@ -137,7 +137,8 @@ namespace CLI {
namespace detail { namespace detail {
template <> template <>
bool lexical_cast<std::pair<std::string, std::string>>(std::string input, std::pair<std::string, std::string> &output) { bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,
std::pair<std::string, std::string> &output) {
auto sep = input.find_first_of(':'); auto sep = input.find_first_of(':');
if((sep == std::string::npos) && (sep > 0)) { if((sep == std::string::npos) && (sep > 0)) {
@ -187,7 +188,7 @@ namespace detail {
// On MSVC and possibly some other new compilers this can be a free standing function without the template // On MSVC and possibly some other new compilers this can be a free standing function without the template
// specialization but this is compiler dependent // specialization but this is compiler dependent
template <> bool lexical_cast<std::complex<double>>(std::string input, std::complex<double> &output) { template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
// regular expression to handle complex numbers of various formats // regular expression to handle complex numbers of various formats
static const std::regex creg( static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
@ -209,8 +210,9 @@ template <> bool lexical_cast<std::complex<double>>(std::string input, std::comp
CLI::detail::trim(strval); CLI::detail::trim(strval);
worked = CLI::detail::lexical_cast(strval, y); worked = CLI::detail::lexical_cast(strval, y);
} else { } else {
CLI::detail::trim(input); std::string ival = input;
worked = CLI::detail::lexical_cast(input, x); CLI::detail::trim(ival);
worked = CLI::detail::lexical_cast(ival, x);
} }
} }
if(worked) { if(worked) {
@ -259,3 +261,187 @@ TEST_F(TApp, AddingComplexParserDetail) {
} }
} }
#endif #endif
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments
template <class X> class objWrapper {
public:
objWrapper() = default;
explicit objWrapper(X obj) : val_{obj} {};
objWrapper(const objWrapper &ow) = default;
template <class TT> objWrapper(const TT &obj) = delete;
objWrapper &operator=(const objWrapper &) = default;
objWrapper &operator=(objWrapper &&) = default;
// delete all other assignment operators
template <typename TT> void operator=(TT &&obj) = delete;
const X &value() const { return val_; }
private:
X val_;
};
// I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions
// so this test will not compile in that compiler
#if !defined(_MSC_VER) || _MSC_VER >= 1910
static_assert(CLI::detail::is_direct_constructible<objWrapper<std::string>, std::string>::value,
"string wrapper isn't properly constructible");
static_assert(!std::is_assignable<objWrapper<std::string>, std::string>::value,
"string wrapper is improperly assignable");
TEST_F(TApp, stringWrapper) {
objWrapper<std::string> sWrapper;
app.add_option("-v", sWrapper);
args = {"-v", "string test"};
run();
EXPECT_EQ(sWrapper.value(), "string test");
}
static_assert(CLI::detail::is_direct_constructible<objWrapper<double>, double>::value,
"double wrapper isn't properly assignable");
static_assert(!CLI::detail::is_direct_constructible<objWrapper<double>, int>::value,
"double wrapper can be assigned from int");
static_assert(!CLI::detail::is_istreamable<objWrapper<double>>::value,
"double wrapper is input streamable and it shouldn't be");
TEST_F(TApp, doubleWrapper) {
objWrapper<double> dWrapper;
app.add_option("-v", dWrapper);
args = {"-v", "2.36"};
run();
EXPECT_EQ(dWrapper.value(), 2.36);
args = {"-v", "thing"};
EXPECT_THROW(run(), CLI::ConversionError);
}
static_assert(CLI::detail::is_direct_constructible<objWrapper<int>, int>::value,
"int wrapper is not constructible from int64");
static_assert(!CLI::detail::is_direct_constructible<objWrapper<int>, double>::value,
"int wrapper is constructible from double");
static_assert(!CLI::detail::is_istreamable<objWrapper<int>>::value,
"int wrapper is input streamable and it shouldn't be");
TEST_F(TApp, intWrapper) {
objWrapper<int> iWrapper;
app.add_option("-v", iWrapper);
args = {"-v", "45"};
run();
EXPECT_EQ(iWrapper.value(), 45);
args = {"-v", "thing"};
EXPECT_THROW(run(), CLI::ConversionError);
}
static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, int>::value,
"float wrapper is constructible from int");
static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, double>::value,
"float wrapper is constructible from double");
static_assert(!CLI::detail::is_istreamable<objWrapper<float>>::value,
"float wrapper is input streamable and it shouldn't be");
TEST_F(TApp, floatWrapper) {
objWrapper<float> iWrapper;
app.add_option<objWrapper<float>, float>("-v", iWrapper);
args = {"-v", "45.3"};
run();
EXPECT_EQ(iWrapper.value(), 45.3f);
args = {"-v", "thing"};
EXPECT_THROW(run(), CLI::ConversionError);
}
#endif
/// simple class to wrap another with a very specific type constructor to test out some of the option assignments
class dobjWrapper {
public:
dobjWrapper() = default;
explicit dobjWrapper(double obj) : dval_{obj} {};
explicit dobjWrapper(int obj) : ival_{obj} {};
double dvalue() const { return dval_; }
int ivalue() const { return ival_; }
private:
double dval_{0.0};
int ival_{0};
};
TEST_F(TApp, dobjWrapper) {
dobjWrapper iWrapper;
app.add_option("-v", iWrapper);
args = {"-v", "45"};
run();
EXPECT_EQ(iWrapper.ivalue(), 45);
EXPECT_EQ(iWrapper.dvalue(), 0.0);
args = {"-v", "thing"};
EXPECT_THROW(run(), CLI::ConversionError);
iWrapper = dobjWrapper{};
args = {"-v", "45.1"};
run();
EXPECT_EQ(iWrapper.ivalue(), 0);
EXPECT_EQ(iWrapper.dvalue(), 45.1);
}
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments
template <class X> class AobjWrapper {
public:
AobjWrapper() = default;
// delete all other constructors
template <class TT> AobjWrapper(TT &&obj) = delete;
// single assignment operator
void operator=(X val) { val_ = val; }
// delete all other assignment operators
template <typename TT> void operator=(TT &&obj) = delete;
const X &value() const { return val_; }
private:
X val_;
};
static_assert(std::is_assignable<AobjWrapper<uint16_t> &, uint16_t>::value,
"AobjWrapper not assignable like it should be ");
TEST_F(TApp, uint16Wrapper) {
AobjWrapper<uint16_t> sWrapper;
app.add_option<AobjWrapper<uint16_t>, uint16_t>("-v", sWrapper);
args = {"-v", "9"};
run();
EXPECT_EQ(sWrapper.value(), 9u);
args = {"-v", "thing"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"-v", "72456245754"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"-v", "-3"};
EXPECT_THROW(run(), CLI::ConversionError);
}

View File

@ -3,6 +3,48 @@
#include "app_helper.hpp" #include "app_helper.hpp"
// You can explicitly enable or disable support
// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too.
// We nest the check for __has_include and it's usage
#ifndef CLI11_STD_OPTIONAL
#ifdef __has_include
#if defined(CLI11_CPP17) && __has_include(<optional>)
#define CLI11_STD_OPTIONAL 1
#else
#define CLI11_STD_OPTIONAL 0
#endif
#else
#define CLI11_STD_OPTIONAL 0
#endif
#endif
#ifndef CLI11_EXPERIMENTAL_OPTIONAL
#define CLI11_EXPERIMENTAL_OPTIONAL 0
#endif
#ifndef CLI11_BOOST_OPTIONAL
#define CLI11_BOOST_OPTIONAL 0
#endif
#if CLI11_BOOST_OPTIONAL
#include <boost/version.hpp>
#if BOOST_VERSION < 106100
#error "This boost::optional version is not supported, use 1.61 or better"
#endif
#endif
#if CLI11_STD_OPTIONAL
#include <optional>
#endif
#if CLI11_EXPERIMENTAL_OPTIONAL
#include <experimental/optional>
#endif
#if CLI11_BOOST_OPTIONAL
#include <boost/optional.hpp>
#include <boost/optional/optional_io.hpp>
#endif
// [CLI11:verbatim]
#if CLI11_STD_OPTIONAL #if CLI11_STD_OPTIONAL
TEST_F(TApp, StdOptionalTest) { TEST_F(TApp, StdOptionalTest) {
@ -55,13 +97,69 @@ TEST_F(TApp, BoostOptionalTest) {
run(); run();
EXPECT_TRUE(opt); EXPECT_TRUE(opt);
EXPECT_EQ(*opt, 1); EXPECT_EQ(*opt, 1);
opt = {};
args = {"--count", "3"}; args = {"--count", "3"};
run(); run();
EXPECT_TRUE(opt); EXPECT_TRUE(opt);
EXPECT_EQ(*opt, 3); EXPECT_EQ(*opt, 3);
} }
TEST_F(TApp, BoostOptionalint64Test) {
boost::optional<int64_t> opt;
app.add_option("-c,--count", opt);
run();
EXPECT_FALSE(opt);
args = {"-c", "1"};
run();
EXPECT_TRUE(opt);
EXPECT_EQ(*opt, 1);
opt = {};
args = {"--count", "3"};
run();
EXPECT_TRUE(opt);
EXPECT_EQ(*opt, 3);
}
TEST_F(TApp, BoostOptionalStringTest) {
boost::optional<std::string> opt;
app.add_option("-s,--string", opt);
run();
EXPECT_FALSE(opt);
args = {"-s", "strval"};
run();
EXPECT_TRUE(opt);
EXPECT_EQ(*opt, "strval");
opt = {};
args = {"--string", "strv"};
run();
EXPECT_TRUE(opt);
EXPECT_EQ(*opt, "strv");
}
TEST_F(TApp, BoostOptionalEnumTest) {
enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 };
boost::optional<eval> opt;
auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt);
optptr->capture_default_str();
auto dstring = optptr->get_default_str();
EXPECT_TRUE(dstring.empty());
run();
EXPECT_FALSE(opt);
args = {"-v", "3"};
run();
EXPECT_TRUE(opt);
EXPECT_TRUE(*opt == eval::val3);
opt = {};
args = {"--val", "1"};
run();
EXPECT_TRUE(opt);
EXPECT_TRUE(*opt == eval::val1);
}
TEST_F(TApp, BoostOptionalVector) { TEST_F(TApp, BoostOptionalVector) {
boost::optional<std::vector<int>> opt; boost::optional<std::vector<int>> opt;
app.add_option_function<std::vector<int>>("-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector") app.add_option_function<std::vector<int>>("-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector")

View File

@ -2,6 +2,15 @@
#include <unordered_map> #include <unordered_map>
#if defined(CLI11_CPP17)
#if defined(__has_include)
#if __has_include(<string_view>)
#include <string_view>
#define CLI11_HAS_STRING_VIEW
#endif
#endif
#endif
TEST_F(TApp, SimpleTransform) { TEST_F(TApp, SimpleTransform) {
int value; int value;
auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}})); auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
@ -112,6 +121,7 @@ TEST_F(TApp, StringViewTransformFn) {
run(); run();
EXPECT_EQ(value, "mapped"); EXPECT_EQ(value, "mapped");
} }
#endif #endif
TEST_F(TApp, SimpleNumericalTransformFn) { TEST_F(TApp, SimpleNumericalTransformFn) {