1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

Support tuple (#307)

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

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

add version to appveyor and add some notes to the readme

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

remove unneeded enum streaming operator

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

work specification of the template operations

remove optional add some templates for options conversions

add the two parameter template for add_option

* Fix some comments from Code review and add more description

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

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

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

* Fix capitalization and some comments on option functions

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

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

* add lexical conversion functions for tuples and other types

* remove the separate vector option and option function

* test out the type names for tuples

* add some more lexical conversion functions and test

* more work on tuples and tests

* fix some merge warnings

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

* tweak some of the template to remove undefined references

* add extra static assert about is_tuple_like

* fix some undefined references in clang

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

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

* add some info to the readme
This commit is contained in:
Philip Top 2019-08-16 06:58:15 -07:00 committed by Henry Schreiner
parent 4bfce43795
commit 127f5388ab
5 changed files with 503 additions and 107 deletions

View File

@ -194,7 +194,7 @@ 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,
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.
variable_to_bind_to, // bool, int, float, vector, 🆕 enum, or string-like, or anything with a defined conversion from a string or that takes an int🚧, double🚧, or string in a constructor. Also allowed are tuples(up to 5 elements) and tuple like structures such as std::array or std::pair.
help_string="")
app.add_option_function<type>(option_name,
@ -202,7 +202,7 @@ app.add_option_function<type>(option_name,
help_string="")
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.
//🚧There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value.
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="")
@ -212,7 +212,7 @@ app.add_flag(option_name,
help_string="")
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 like add_option
variable_to_bind_to, // bool, int, 🆕 float, 🆕 vector, 🆕 enum, or 🆕 string-like, or 🆕 any singular object with a defined conversion from a string like add_option
help_string="")
app.add_flag_function(option_name, // 🆕
@ -249,7 +249,7 @@ The `add_option_function<type>(...` function will typically require the template
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
which would first verify the input is convertible to an unsigned int before assigning it. Or using some variant type
```
using vtype=std::variant<int, double, std::string>;
vtype v1;
@ -261,6 +261,13 @@ otherwise the output would default to a string. The add_option can be used with
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.
Vector types can also be used int the two parameter template overload
```
std::vector<double> v1;
app.add_option<std::vector<double>,int>("--vs",v1);
```
would load a vector of doubles but ensure all values can be represented as integers.
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:

View File

@ -468,35 +468,37 @@ class App {
/// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
template <typename T,
typename XC = T,
enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy>
template <typename T, typename XC = T, enable_if_t<!std::is_const<XC>::value, detail::enabler> = detail::dummy>
Option *add_option(std::string option_name,
T &variable, ///< The variable to set
std::string option_description = "",
bool defaulted = false) {
auto fun = [&variable](CLI::results_t res) { // comment for spacing
return detail::lexical_assign<T, XC>(res[0], variable);
return detail::lexical_conversion<T, XC>(res, variable);
};
Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
return std::string(CLI::detail::checked_to_string<T, XC>(variable));
return CLI::detail::checked_to_string<T, XC>(variable);
});
opt->type_name(detail::type_name<XC>());
// these must be actual variable since (std::max) sometimes is defined in terms of references and references to
// structs used in the evaluation can be temporary so that would cause issues.
auto Tcount = detail::type_count<T>::value;
auto XCcount = detail::type_count<XC>::value;
opt->type_size((std::max)(Tcount, XCcount));
return opt;
}
/// Add option for a callback of a specific type
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
template <typename T>
Option *add_option_function(std::string option_name,
const std::function<void(const T &)> &func, ///< the callback to execute
std::string option_description = "") {
auto fun = [func](CLI::results_t res) {
T variable;
bool result = detail::lexical_cast(res[0], variable);
bool result = detail::lexical_conversion<T, T>(res, variable);
if(result) {
func(variable);
}
@ -505,6 +507,7 @@ class App {
Option *opt = add_option(option_name, std::move(fun), option_description, false);
opt->type_name(detail::type_name<T>());
opt->type_size(detail::type_count<T>::value);
return opt;
}
@ -521,65 +524,6 @@ class App {
return add_option(option_name, CLI::callback_t(), option_description, false);
}
/// Add option for vectors
template <typename T>
Option *add_option(std::string option_name,
std::vector<T> &variable, ///< The variable vector to set
std::string option_description = "",
bool defaulted = false) {
auto fun = [&variable](CLI::results_t res) {
bool retval = true;
variable.clear();
variable.reserve(res.size());
for(const auto &elem : res) {
variable.emplace_back();
retval &= detail::lexical_cast(elem, variable.back());
}
return (!variable.empty()) && retval;
};
auto default_function = [&variable]() {
std::vector<std::string> defaults;
defaults.resize(variable.size());
std::transform(variable.begin(), variable.end(), defaults.begin(), [](T &val) {
return std::string(CLI::detail::to_string(val));
});
return std::string("[" + detail::join(defaults) + "]");
};
Option *opt = add_option(option_name, fun, option_description, defaulted, default_function);
opt->type_name(detail::type_name<T>())->type_size(-1);
return opt;
}
/// Add option for a vector callback of a specific type
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
Option *add_option_function(std::string option_name,
const std::function<void(const T &)> &func, ///< the callback to execute
std::string option_description = "") {
CLI::callback_t fun = [func](CLI::results_t res) {
T values;
bool retval = true;
values.reserve(res.size());
for(const auto &elem : res) {
values.emplace_back();
retval &= detail::lexical_cast(elem, values.back());
}
if(retval) {
func(values);
}
return retval;
};
Option *opt = add_option(option_name, std::move(fun), std::move(option_description), false);
opt->type_name(detail::type_name<T>())->type_size(-1);
return opt;
}
/// Set a help flag, replace the existing one if present
Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
// take flag_description by const reference otherwise add_flag tries to assign to help_description

View File

@ -142,7 +142,7 @@ template <typename T, typename C> class is_direct_constructible {
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T, C>(0))::value;
static constexpr bool value = decltype(test<T, C>(0))::value;
};
#ifdef __GNUC__
#pragma GCC diagnostic pop
@ -158,7 +158,7 @@ template <typename T, typename S = std::ostringstream> class is_ostreamable {
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T, S>(0))::value;
static constexpr bool value = decltype(test<T, S>(0))::value;
};
/// Check for input streamability
@ -169,7 +169,7 @@ template <typename T, typename S = std::istringstream> class is_istreamable {
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T, S>(0))::value;
static constexpr bool value = decltype(test<T, S>(0))::value;
};
/// Templated operation to get a value from a stream
@ -186,6 +186,18 @@ bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
return false;
}
// Check for tuple like types, as in classes with a tuple_size type trait
template <typename S> class is_tuple_like {
template <typename SS>
// static auto test(int)
// -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
static auto test(int) -> decltype(std::tuple_size<SS>::value, std::true_type{});
template <typename> static auto test(...) -> std::false_type;
public:
static constexpr bool value = decltype(test<S>(0))::value;
};
/// 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>
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
@ -204,12 +216,30 @@ std::string to_string(T &&value) {
/// If conversion is not supported, return an empty string (streaming is not supported for that type)
template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value, detail::enabler> =
detail::dummy>
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
!is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&) {
return std::string{};
}
/// convert a vector to a string
template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&variable) {
std::vector<std::string> defaults;
defaults.reserve(variable.size());
auto cval = variable.begin();
auto end = variable.end();
while(cval != end) {
defaults.emplace_back(CLI::detail::to_string(*cval));
++cval;
}
return std::string("[" + detail::join(defaults) + "]");
}
/// special template overload
template <typename T1,
typename T2,
@ -228,6 +258,25 @@ std::string checked_to_string(T &&) {
return std::string{};
}
/// This will only trigger for actual void type
template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
/// Set of overloads to get the type size of an object
template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
static constexpr int value{std::tuple_size<T>::value};
};
/// Type size for regular object types that do not look like a tuple
template <typename T>
struct type_count<
T,
typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> {
static constexpr int value{1};
};
/// Type size of types that look like a vector
template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> {
static constexpr int value{-1};
};
// Enumeration of the different supported categorizations of objects
enum objCategory : int {
integral_value = 2,
@ -239,6 +288,7 @@ enum objCategory : int {
double_constructible = 14,
integer_constructible = 16,
vector_value = 30,
tuple_value = 35,
// string assignable or greater used in a condition so anything string like must come last
string_assignable = 50,
string_constructible = 60,
@ -308,36 +358,49 @@ template <typename T> struct uncommon_type {
!std::is_enum<T>::value,
std::true_type,
std::false_type>::type;
static const bool value = type::value;
static constexpr bool value = type::value;
};
/// Assignable from double or int
template <typename T>
struct classify_object<T,
typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
is_direct_constructible<T, int>::value>::type> {
struct classify_object<
T,
typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
static constexpr objCategory value{number_constructible};
};
/// Assignable from int
template <typename T>
struct classify_object<T,
typename std::enable_if<uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
is_direct_constructible<T, int>::value>::type> {
static const objCategory value{integer_constructible};
struct classify_object<
T,
typename std::enable_if<uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
static constexpr objCategory value{integer_constructible};
};
/// Assignable from double
template <typename T>
struct classify_object<T,
typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
!is_direct_constructible<T, int>::value>::type> {
static const objCategory value{double_constructible};
struct classify_object<
T,
typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
!is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
static constexpr objCategory value{double_constructible};
};
/// vector type
/// Tuple type
template <typename T>
struct classify_object<
T,
typename std::enable_if<(is_tuple_like<T>::value && uncommon_type<T>::value &&
!is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value) ||
type_count<T>::value >= 2>::type> {
static constexpr objCategory value{tuple_value};
};
/// Vector type
template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> {
static const objCategory value{vector_value};
static constexpr objCategory value{vector_value};
};
// Type name print
@ -367,11 +430,6 @@ constexpr const char *type_name() {
return "FLOAT";
}
/// This one should not be used, since vector types print the internal type
template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>
constexpr const char *type_name() {
return "VECTOR";
}
/// Print name for enumeration types
template <typename T, enable_if_t<classify_object<T>::value == enumeration, detail::enabler> = detail::dummy>
constexpr const char *type_name() {
@ -390,6 +448,60 @@ constexpr const char *type_name() {
return "TEXT";
}
/// This one should not be used normally, since vector types print the internal type
template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>
constexpr const char *type_name() {
return type_name<typename T::value_type>();
}
/// Print name for tuple types
template <
typename T,
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 1, detail::enabler> = detail::dummy>
std::string type_name() {
return type_name<typename std::tuple_element<0, T>::type>();
}
/// Print type name for 2 element tuples
template <
typename T,
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 2, detail::enabler> = detail::dummy>
std::string type_name() {
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
type_name<typename std::tuple_element<1, T>::type>() + "]";
}
/// Print type name for 3 element tuples
template <
typename T,
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 3, detail::enabler> = detail::dummy>
std::string type_name() {
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
type_name<typename std::tuple_element<1, T>::type>() + "," +
type_name<typename std::tuple_element<2, T>::type>() + "]";
}
/// Print type name for 4 element tuples
template <
typename T,
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 4, detail::enabler> = detail::dummy>
std::string type_name() {
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
type_name<typename std::tuple_element<1, T>::type>() + "," +
type_name<typename std::tuple_element<2, T>::type>() + "," +
type_name<typename std::tuple_element<3, T>::type>() + "]";
}
/// Print type name for 5 element tuples
template <
typename T,
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 5, detail::enabler> = detail::dummy>
std::string type_name() {
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
type_name<typename std::tuple_element<1, T>::type>() + "," +
type_name<typename std::tuple_element<2, T>::type>() + "," +
type_name<typename std::tuple_element<3, T>::type>() + "," +
type_name<typename std::tuple_element<4, T>::type>() + "]";
}
// Lexical cast
/// Convert a flag into an integer value typically binary flags
@ -574,19 +686,19 @@ bool lexical_cast(const std::string &input, T &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>
template <typename T, typename 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,
typename T,
typename 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);
bool parse_result = lexical_cast<XC>(input, val);
if(parse_result) {
output = val;
}
@ -594,8 +706,8 @@ bool lexical_assign(const std::string &input, T &output) {
}
/// Assign a value from a lexical cast through constructing a value and move assigning it
template <class T,
class XC,
template <typename T,
typename 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>
@ -607,6 +719,160 @@ bool lexical_assign(const std::string &input, T &output) {
}
return parse_result;
}
/// Lexical conversion if there is only one element
template <typename T,
typename XC,
enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value, detail::enabler> =
detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
return lexical_assign<T, XC>(strings[0], output);
}
/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor
template <typename T,
typename XC,
enable_if_t<type_count<T>::value == 1 && type_count<XC>::value == 2, detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
typename std::tuple_element<0, XC>::type v1;
typename std::tuple_element<1, XC>::type v2;
bool retval = lexical_cast(strings[0], v1);
if(strings.size() > 1) {
retval &= lexical_cast(strings[1], v2);
}
if(retval) {
output = T{v1, v2};
}
return retval;
}
/// Lexical conversion of a vector types
template <class T,
class XC,
enable_if_t<type_count<T>::value == -1 && type_count<XC>::value == -1, detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
bool retval = true;
output.clear();
output.reserve(strings.size());
for(const auto &elem : strings) {
output.emplace_back();
retval &= lexical_assign<typename T::value_type, typename XC::value_type>(elem, output.back());
}
return (!output.empty()) && retval;
}
/// Conversion to a vector type using a particular single type as the conversion type
template <class T,
class XC,
enable_if_t<(type_count<T>::value == -1) && (type_count<XC>::value == 1), detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
bool retval = true;
output.clear();
output.reserve(strings.size());
for(const auto &elem : strings) {
output.emplace_back();
retval &= lexical_assign<typename T::value_type, XC>(elem, output.back());
}
return (!output.empty()) && retval;
}
/// Conversion for single element tuple and single element tuple conversion type
template <class T,
class XC,
enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && is_tuple_like<XC>::value,
detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
static_assert(type_count<T>::value == type_count<XC>::value,
"when using converting to tuples different cross conversion are not possible");
bool retval = lexical_assign<typename std::tuple_element<0, T>::type, typename std::tuple_element<0, XC>::type>(
strings[0], std::get<0>(output));
return retval;
}
/// Conversion for single element tuple and single defined type
template <class T,
class XC,
enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && !is_tuple_like<XC>::value,
detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
static_assert(type_count<T>::value == type_count<XC>::value,
"when using converting to tuples different cross conversion are not possible");
bool retval = lexical_assign<typename std::tuple_element<0, T>::type, XC>(strings[0], std::get<0>(output));
return retval;
}
/// conversion for two element tuple
template <class T, class XC, enable_if_t<type_count<T>::value == 2, detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
static_assert(type_count<T>::value == type_count<XC>::value,
"when using converting to tuples different cross conversion are not possible");
bool retval = lexical_cast(strings[0], std::get<0>(output));
if(strings.size() > 1) {
retval &= lexical_cast(strings[1], std::get<1>(output));
}
return retval;
}
/// conversion for three element tuple
template <class T, class XC, enable_if_t<type_count<T>::value == 3, detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
static_assert(type_count<T>::value == type_count<XC>::value,
"when using converting to tuples different cross conversion are not possible");
bool retval = lexical_cast(strings[0], std::get<0>(output));
if(strings.size() > 1) {
retval &= lexical_cast(strings[1], std::get<1>(output));
}
if(strings.size() > 2) {
retval &= lexical_cast(strings[2], std::get<2>(output));
}
return retval;
}
/// conversion for four element tuple
template <class T, class XC, enable_if_t<type_count<T>::value == 4, detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
static_assert(type_count<T>::value == type_count<XC>::value,
"when using converting to tuples different cross conversion are not possible");
bool retval = lexical_cast(strings[0], std::get<0>(output));
if(strings.size() > 1) {
retval &= lexical_cast(strings[1], std::get<1>(output));
}
if(strings.size() > 2) {
retval &= lexical_cast(strings[2], std::get<2>(output));
}
if(strings.size() > 3) {
retval &= lexical_cast(strings[3], std::get<3>(output));
}
return retval;
}
/// conversion for five element tuple
template <class T, class XC, enable_if_t<type_count<T>::value == 5, detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
static_assert(type_count<T>::value == type_count<XC>::value,
"when using converting to tuples different cross conversion are not possible");
bool retval = lexical_cast(strings[0], std::get<0>(output));
if(strings.size() > 1) {
retval &= lexical_cast(strings[1], std::get<1>(output));
}
if(strings.size() > 2) {
retval &= lexical_cast(strings[2], std::get<2>(output));
}
if(strings.size() > 3) {
retval &= lexical_cast(strings[3], std::get<3>(output));
}
if(strings.size() > 4) {
retval &= lexical_cast(strings[4], std::get<4>(output));
}
return retval;
}
/// 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

View File

@ -1,11 +1,13 @@
#include "app_helper.hpp"
#include <array>
#include <climits>
#include <complex>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <string>
#include <tuple>
class NotStreamable {};
@ -25,6 +27,30 @@ TEST(TypeTools, Streaming) {
EXPECT_EQ(CLI::detail::to_string(std::string("string")), std::string("string"));
}
TEST(TypeTools, tuple) {
EXPECT_FALSE(CLI::detail::is_tuple_like<int>::value);
EXPECT_FALSE(CLI::detail::is_tuple_like<std::vector<double>>::value);
auto v = CLI::detail::is_tuple_like<std::tuple<double, int>>::value;
EXPECT_TRUE(v);
v = CLI::detail::is_tuple_like<std::tuple<double, double, double>>::value;
EXPECT_TRUE(v);
}
TEST(TypeTools, type_size) {
auto V = CLI::detail::type_count<int>::value;
EXPECT_EQ(V, 1);
V = CLI::detail::type_count<void>::value;
EXPECT_EQ(V, 0);
V = CLI::detail::type_count<std::vector<double>>::value;
EXPECT_EQ(V, -1);
V = CLI::detail::type_count<std::tuple<double, int>>::value;
EXPECT_EQ(V, 2);
V = CLI::detail::type_count<std::tuple<std::string, double, int>>::value;
EXPECT_EQ(V, 3);
V = CLI::detail::type_count<std::array<std::string, 5>>::value;
EXPECT_EQ(V, 5);
}
TEST(Split, SimpleByToken) {
auto out = CLI::detail::split("one.two.three", '.');
ASSERT_EQ(3u, out.size());
@ -782,7 +808,33 @@ TEST(Types, TypeName) {
EXPECT_EQ("FLOAT", float_name);
std::string vector_name = CLI::detail::type_name<std::vector<int>>();
EXPECT_EQ("VECTOR", vector_name);
EXPECT_EQ("INT", vector_name);
vector_name = CLI::detail::type_name<std::vector<double>>();
EXPECT_EQ("FLOAT", vector_name);
vector_name = CLI::detail::type_name<std::vector<std::vector<unsigned char>>>();
EXPECT_EQ("UINT", vector_name);
auto vclass = CLI::detail::classify_object<std::tuple<double>>::value;
EXPECT_EQ(vclass, CLI::detail::objCategory::number_constructible);
std::string tuple_name = CLI::detail::type_name<std::tuple<double>>();
EXPECT_EQ("FLOAT", tuple_name);
static_assert(CLI::detail::classify_object<std::tuple<int, std::string>>::value ==
CLI::detail::objCategory::tuple_value,
"tuple<int,string> does not read like a tuple");
tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>();
EXPECT_EQ("[INT,TEXT]", tuple_name);
tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>();
EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name);
tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int>>();
EXPECT_EQ("[INT,TEXT,FLOAT,UINT]", tuple_name);
tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int, std::string>>();
EXPECT_EQ("[INT,TEXT,FLOAT,UINT,TEXT]", tuple_name);
std::string text_name = CLI::detail::type_name<std::string>();
EXPECT_EQ("TEXT", text_name);
@ -793,6 +845,13 @@ TEST(Types, TypeName) {
enum class test { test1, test2, test3 };
std::string enum_name = CLI::detail::type_name<test>();
EXPECT_EQ("ENUM", enum_name);
vclass = CLI::detail::classify_object<std::tuple<test>>::value;
EXPECT_EQ(vclass, CLI::detail::objCategory::tuple_value);
static_assert(CLI::detail::classify_object<std::tuple<test>>::value == CLI::detail::objCategory::tuple_value,
"tuple<test> does not classify as a tuple");
std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>();
EXPECT_EQ("ENUM", enum_name2);
}
TEST(Types, OverflowSmall) {
@ -906,6 +965,121 @@ TEST(Types, LexicalCastEnum) {
EXPECT_EQ(output2, t2::enum3);
}
TEST(Types, LexicalConversionDouble) {
CLI::results_t input = {"9.12"};
long double x;
bool res = CLI::detail::lexical_conversion<long double, double>(input, x);
EXPECT_TRUE(res);
EXPECT_FLOAT_EQ((float)9.12, (float)x);
CLI::results_t bad_input = {"hello"};
res = CLI::detail::lexical_conversion<long double, double>(input, x);
EXPECT_TRUE(res);
}
TEST(Types, LexicalConversionDoubleTuple) {
CLI::results_t input = {"9.12"};
std::tuple<double> x;
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_TRUE(res);
EXPECT_DOUBLE_EQ(9.12, std::get<0>(x));
CLI::results_t bad_input = {"hello"};
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_TRUE(res);
}
TEST(Types, LexicalConversionVectorDouble) {
CLI::results_t input = {"9.12", "10.79", "-3.54"};
std::vector<double> x;
bool res = CLI::detail::lexical_conversion<std::vector<double>, double>(input, x);
EXPECT_TRUE(res);
EXPECT_EQ(x.size(), 3u);
EXPECT_DOUBLE_EQ(x[2], -3.54);
res = CLI::detail::lexical_conversion<std::vector<double>, std::vector<double>>(input, x);
EXPECT_TRUE(res);
EXPECT_EQ(x.size(), 3u);
EXPECT_DOUBLE_EQ(x[2], -3.54);
}
static_assert(!CLI::detail::is_tuple_like<std::vector<double>>::value, "vector should not be like a tuple");
static_assert(CLI::detail::is_tuple_like<std::pair<double, double>>::value, "pair of double should be like a tuple");
static_assert(CLI::detail::is_tuple_like<std::array<double, 4>>::value, "std::array should be like a tuple");
static_assert(!CLI::detail::is_tuple_like<std::string>::value, "std::string should not be like a tuple");
static_assert(!CLI::detail::is_tuple_like<double>::value, "double should not be like a tuple");
static_assert(CLI::detail::is_tuple_like<std::tuple<double, int, double>>::value, "tuple should look like a tuple");
TEST(Types, LexicalConversionTuple2) {
CLI::results_t input = {"9.12", "19"};
std::tuple<double, int> x;
static_assert(CLI::detail::is_tuple_like<decltype(x)>::value,
"tuple type must have is_tuple_like trait to be true");
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_TRUE(res);
EXPECT_EQ(std::get<1>(x), 19);
EXPECT_DOUBLE_EQ(std::get<0>(x), 9.12);
input = {"19", "9.12"};
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_FALSE(res);
}
TEST(Types, LexicalConversionTuple3) {
CLI::results_t input = {"9.12", "19", "hippo"};
std::tuple<double, int, std::string> x;
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_TRUE(res);
EXPECT_EQ(std::get<1>(x), 19);
EXPECT_DOUBLE_EQ(std::get<0>(x), 9.12);
EXPECT_EQ(std::get<2>(x), "hippo");
input = {"19", "9.12"};
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_FALSE(res);
}
TEST(Types, LexicalConversionTuple4) {
CLI::results_t input = {"9.12", "19", "18.6", "5.87"};
std::array<double, 4> x;
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_TRUE(res);
EXPECT_DOUBLE_EQ(std::get<1>(x), 19);
EXPECT_DOUBLE_EQ(x[0], 9.12);
EXPECT_DOUBLE_EQ(x[2], 18.6);
EXPECT_DOUBLE_EQ(x[3], 5.87);
input = {"19", "9.12", "hippo"};
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_FALSE(res);
}
TEST(Types, LexicalConversionTuple5) {
CLI::results_t input = {"9", "19", "18", "5", "235235"};
std::array<unsigned int, 5> x;
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_TRUE(res);
EXPECT_EQ(std::get<1>(x), 19u);
EXPECT_EQ(x[0], 9u);
EXPECT_EQ(x[2], 18u);
EXPECT_EQ(x[3], 5u);
EXPECT_EQ(x[4], 235235u);
input = {"19", "9.12", "hippo"};
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
EXPECT_FALSE(res);
}
TEST(Types, LexicalConversionXomplwz) {
CLI::results_t input = {"5.1", "3.5"};
std::complex<double> x;
bool res = CLI::detail::lexical_conversion<std::complex<double>, std::array<double, 2>>(input, x);
EXPECT_TRUE(res);
EXPECT_EQ(x.real(), 5.1);
EXPECT_EQ(x.imag(), 3.5);
}
TEST(FixNewLines, BasicCheck) {
std::string input = "one\ntwo";
std::string output = "one\n; two";

View File

@ -130,15 +130,20 @@ TEST_F(TApp, BuiltinComplexFail) {
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
class spair {
public:
spair() = default;
spair(const std::string &s1, const std::string &s2) : first(s1), second(s2) {}
std::string first;
std::string second;
};
// an example of custom converter that can be used to add new parsing options
// On MSVC and possibly some other new compilers this can be a free standing function without the template
// specialization but this is compiler dependent
namespace CLI {
namespace detail {
template <>
bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,
std::pair<std::string, std::string> &output) {
template <> bool lexical_cast<spair>(const std::string &input, spair &output) {
auto sep = input.find_first_of(':');
if((sep == std::string::npos) && (sep > 0)) {
@ -151,7 +156,7 @@ bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,
} // namespace CLI
TEST_F(TApp, custom_string_converter) {
std::pair<std::string, std::string> val;
spair val;
app.add_option("-d,--dual_string", val);
args = {"-d", "string1:string2"};
@ -162,7 +167,7 @@ TEST_F(TApp, custom_string_converter) {
}
TEST_F(TApp, custom_string_converterFail) {
std::pair<std::string, std::string> val;
spair val;
app.add_option("-d,--dual_string", val);
args = {"-d", "string2"};