Skip to content

Commit

Permalink
Support tuple (#307)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
phlptp authored and henryiii committed Aug 16, 2019
1 parent 4bfce43 commit 127f538
Show file tree
Hide file tree
Showing 5 changed files with 503 additions and 107 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ 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,
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="")

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="")
Expand All @@ -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, // 🆕
Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand Down
78 changes: 11 additions & 67 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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;
}

Expand All @@ -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
Expand Down
Loading

0 comments on commit 127f538

Please sign in to comment.