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

Using ADL everywhere for lexical_cast (#820)

* Using ADL everywhere for lexical_cast

* Fixes in docs

* Add a test for old extension mechanism

* style: pre-commit.ci fixes

* Make gcc happy

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Philip Top <phlptp@gmail.com>
This commit is contained in:
captainurist 2023-01-02 23:09:27 +08:00 committed by GitHub
parent fbe1763675
commit 3897109e51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 135 additions and 38 deletions

View File

@ -1435,11 +1435,10 @@ provide a custom `operator>>` with an `istream` (inside the CLI namespace is
fine if you don't want to interfere with an existing `operator>>`). fine if you don't want to interfere with an existing `operator>>`).
If you wanted to extend this to support a completely new type, use a lambda or If you wanted to extend this to support a completely new type, use a lambda or
add a specialization of the `lexical_cast` function template in the namespace of add an overload of the `lexical_cast` function in the namespace of the type you
the type you need to convert to. Some examples of some new parsers for need to convert to. Some examples of some new parsers for `complex<double>` that
`complex<double>` that support all of the features of a standard `add_options` support all of the features of a standard `add_options` call are in
call are in [one of the tests](./tests/NewParseTest.cpp). A simpler example is [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below:
shown below:
#### Example #### Example

View File

@ -8,9 +8,9 @@ classes or inheritance. This is accomplished through lambda functions.
This looks like: This looks like:
```cpp ```cpp
Option* add_option(string name, T item) { Option* add_option(string name, T &item) {
this->function = [&item](string value){ this->function = [&item](string value){
item = detail::lexical_cast<T>(value); return lexical_cast(value, item);
} }
} }
``` ```

View File

@ -626,7 +626,8 @@ class App {
std::string flag_description = "") { std::string flag_description = "") {
CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
return CLI::detail::lexical_cast(res[0], flag_result); using CLI::detail::lexical_cast;
return lexical_cast(res[0], flag_result);
}; };
auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
return detail::default_flag_modifiers<T>(opt); return detail::default_flag_modifiers<T>(opt);
@ -642,8 +643,9 @@ class App {
CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { CLI::callback_t fun = [&flag_results](const CLI::results_t &res) {
bool retval = true; bool retval = true;
for(const auto &elem : res) { for(const auto &elem : res) {
using CLI::detail::lexical_cast;
flag_results.emplace_back(); flag_results.emplace_back();
retval &= detail::lexical_cast(elem, flag_results.back()); retval &= lexical_cast(elem, flag_results.back());
} }
return retval; return retval;
}; };

View File

@ -966,18 +966,18 @@ bool lexical_cast(const std::string &input, T &output) {
bool worked = false; bool worked = false;
auto nloc = str1.find_last_of("+-"); auto nloc = str1.find_last_of("+-");
if(nloc != std::string::npos && nloc > 0) { if(nloc != std::string::npos && nloc > 0) {
worked = detail::lexical_cast(str1.substr(0, nloc), x); worked = lexical_cast(str1.substr(0, nloc), x);
str1 = str1.substr(nloc); str1 = str1.substr(nloc);
if(str1.back() == 'i' || str1.back() == 'j') if(str1.back() == 'i' || str1.back() == 'j')
str1.pop_back(); str1.pop_back();
worked = worked && detail::lexical_cast(str1, y); worked = worked && lexical_cast(str1, y);
} else { } else {
if(str1.back() == 'i' || str1.back() == 'j') { if(str1.back() == 'i' || str1.back() == 'j') {
str1.pop_back(); str1.pop_back();
worked = detail::lexical_cast(str1, y); worked = lexical_cast(str1, y);
x = XC{0}; x = XC{0};
} else { } else {
worked = detail::lexical_cast(str1, x); worked = lexical_cast(str1, x);
y = XC{0}; y = XC{0};
} }
} }
@ -1198,7 +1198,7 @@ template <typename AssignTo,
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) { bool lexical_assign(const std::string &input, AssignTo &output) {
ConvertTo val{}; ConvertTo val{};
bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true; bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true;
if(parse_result) { if(parse_result) {
output = val; output = val;
} }
@ -1214,7 +1214,7 @@ template <
detail::enabler> = detail::dummy> detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) { bool lexical_assign(const std::string &input, AssignTo &output) {
ConvertTo val{}; ConvertTo val{};
bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val); bool parse_result = input.empty() ? true : lexical_cast(input, val);
if(parse_result) { if(parse_result) {
output = AssignTo(val); // use () form of constructor to allow some implicit conversions output = AssignTo(val); // use () form of constructor to allow some implicit conversions
} }
@ -1292,7 +1292,7 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu
if(str1.back() == 'i' || str1.back() == 'j') { if(str1.back() == 'i' || str1.back() == 'j') {
str1.pop_back(); str1.pop_back();
} }
auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y);
if(worked) { if(worked) {
output = ConvertTo{x, y}; output = ConvertTo{x, y};
} }
@ -1556,7 +1556,7 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) {
std::string output; std::string output;
for(const auto &arg : values) { for(const auto &arg : values) {
double tv{0.0}; double tv{0.0};
auto comp = detail::lexical_cast<double>(arg, tv); auto comp = lexical_cast(arg, tv);
if(!comp) { if(!comp) {
try { try {
tv = static_cast<double>(detail::to_flag_value(arg)); tv = static_cast<double>(detail::to_flag_value(arg));

View File

@ -270,8 +270,9 @@ template <typename DesiredType> class TypeValidator : public Validator {
public: public:
explicit TypeValidator(const std::string &validator_name) explicit TypeValidator(const std::string &validator_name)
: Validator(validator_name, [](std::string &input_string) { : Validator(validator_name, [](std::string &input_string) {
using CLI::detail::lexical_cast;
auto val = DesiredType(); auto val = DesiredType();
if(!detail::lexical_cast(input_string, val)) { if(!lexical_cast(input_string, val)) {
return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>(); return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
} }
return std::string(); return std::string();
@ -305,8 +306,9 @@ class Range : public Validator {
} }
func_ = [min_val, max_val](std::string &input) { func_ = [min_val, max_val](std::string &input) {
using CLI::detail::lexical_cast;
T val; T val;
bool converted = detail::lexical_cast(input, val); bool converted = lexical_cast(input, val);
if((!converted) || (val < min_val || val > max_val)) { if((!converted) || (val < min_val || val > max_val)) {
std::stringstream out; std::stringstream out;
out << "Value " << input << " not in range ["; out << "Value " << input << " not in range [";
@ -342,8 +344,9 @@ class Bound : public Validator {
description(out.str()); description(out.str());
func_ = [min_val, max_val](std::string &input) { func_ = [min_val, max_val](std::string &input) {
using CLI::detail::lexical_cast;
T val; T val;
bool converted = detail::lexical_cast(input, val); bool converted = lexical_cast(input, val);
if(!converted) { if(!converted) {
return std::string("Value ") + input + " could not be converted"; return std::string("Value ") + input + " could not be converted";
} }
@ -534,8 +537,9 @@ class IsMember : public Validator {
// This is the function that validates // This is the function that validates
// It stores a copy of the set pointer-like, so shared_ptr will stay alive // It stores a copy of the set pointer-like, so shared_ptr will stay alive
func_ = [set, filter_fn](std::string &input) { func_ = [set, filter_fn](std::string &input) {
using CLI::detail::lexical_cast;
local_item_t b; local_item_t b;
if(!detail::lexical_cast(input, b)) { if(!lexical_cast(input, b)) {
throw ValidationError(input); // name is added later throw ValidationError(input); // name is added later
} }
if(filter_fn) { if(filter_fn) {
@ -602,8 +606,9 @@ class Transformer : public Validator {
desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
func_ = [mapping, filter_fn](std::string &input) { func_ = [mapping, filter_fn](std::string &input) {
using CLI::detail::lexical_cast;
local_item_t b; local_item_t b;
if(!detail::lexical_cast(input, b)) { if(!lexical_cast(input, b)) {
return std::string(); return std::string();
// there is no possible way we can match anything in the mapping if we can't convert so just return // there is no possible way we can match anything in the mapping if we can't convert so just return
} }
@ -671,8 +676,9 @@ class CheckedTransformer : public Validator {
desc_function_ = tfunc; desc_function_ = tfunc;
func_ = [mapping, tfunc, filter_fn](std::string &input) { func_ = [mapping, tfunc, filter_fn](std::string &input) {
using CLI::detail::lexical_cast;
local_item_t b; local_item_t b;
bool converted = detail::lexical_cast(input, b); bool converted = lexical_cast(input, b);
if(converted) { if(converted) {
if(filter_fn) { if(filter_fn) {
b = filter_fn(b); b = filter_fn(b);
@ -774,7 +780,8 @@ class AsNumberWithUnit : public Validator {
unit = detail::to_lower(unit); unit = detail::to_lower(unit);
} }
if(unit.empty()) { if(unit.empty()) {
if(!detail::lexical_cast(input, num)) { using CLI::detail::lexical_cast;
if(!lexical_cast(input, num)) {
throw ValidationError(std::string("Value ") + input + " could not be converted to " + throw ValidationError(std::string("Value ") + input + " could not be converted to " +
detail::type_name<Number>()); detail::type_name<Number>());
} }
@ -792,7 +799,8 @@ class AsNumberWithUnit : public Validator {
} }
if(!input.empty()) { if(!input.empty()) {
bool converted = detail::lexical_cast(input, num); using CLI::detail::lexical_cast;
bool converted = lexical_cast(input, num);
if(!converted) { if(!converted) {
throw ValidationError(std::string("Value ") + input + " could not be converted to " + throw ValidationError(std::string("Value ") + input + " could not be converted to " +
detail::type_name<Number>()); detail::type_name<Number>());

View File

@ -265,8 +265,9 @@ CLI11_INLINE Option *App::add_flag_callback(std::string flag_name,
std::string flag_description) { std::string flag_description) {
CLI::callback_t fun = [function](const CLI::results_t &res) { CLI::callback_t fun = [function](const CLI::results_t &res) {
using CLI::detail::lexical_cast;
bool trigger{false}; bool trigger{false};
auto result = CLI::detail::lexical_cast(res[0], trigger); auto result = lexical_cast(res[0], trigger);
if(result && trigger) { if(result && trigger) {
function(); function();
} }
@ -281,8 +282,9 @@ App::add_flag_function(std::string flag_name,
std::string flag_description) { std::string flag_description) {
CLI::callback_t fun = [function](const CLI::results_t &res) { CLI::callback_t fun = [function](const CLI::results_t &res) {
using CLI::detail::lexical_cast;
std::int64_t flag_count{0}; std::int64_t flag_count{0};
CLI::detail::lexical_cast(res[0], flag_count); lexical_cast(res[0], flag_count);
function(flag_count); function(flag_count);
return true; return true;
}; };

View File

@ -31,8 +31,9 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string
} }
// floating point conversion can convert some hex codes, but don't try that here // floating point conversion can convert some hex codes, but don't try that here
if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
using CLI::detail::lexical_cast;
double val = 0.0; double val = 0.0;
if(detail::lexical_cast(arg, val)) { if(lexical_cast(arg, val)) {
return arg; return arg;
} }
} }

View File

@ -219,7 +219,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") {
} }
int num = 0; int num = 0;
for(const auto &var : result) { for(const auto &var : result) {
bool retval = detail::lexical_cast(var, num); using CLI::detail::lexical_cast;
bool retval = lexical_cast(var, num);
if(!retval) { if(!retval) {
return std::string("Failed parsing number (") + var + ')'; return std::string("Failed parsing number (") + var + ')';
} }

View File

@ -163,14 +163,10 @@ class spair {
std::string first{}; std::string first{};
std::string second{}; 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<spair>(const std::string &input, spair &output) {
// Example of a custom converter that can be used to add new parsing options.
// It will be found via argument-dependent lookup, so should be in the same namespace as the `spair` type.
bool lexical_cast(const std::string &input, spair &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)) {
return false; return false;
@ -178,8 +174,6 @@ template <> bool lexical_cast<spair>(const std::string &input, spair &output) {
output = {input.substr(0, sep), input.substr(sep + 1)}; output = {input.substr(0, sep), input.substr(sep + 1)};
return true; return true;
} }
} // namespace detail
} // namespace CLI
TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") { TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") {
spair val; spair val;
@ -201,6 +195,96 @@ TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") {
CHECK_THROWS_AS(run(), CLI::ConversionError); CHECK_THROWS_AS(run(), CLI::ConversionError);
} }
/// Wrapper with an unconvenient interface
template <class T> class badlywrapped {
public:
badlywrapped() : value() {}
CLI11_NODISCARD T get() const { return value; }
void set(T val) { value = val; }
private:
T value;
};
// Example of a custom converter for a template type.
// It will be found via argument-dependent lookup, so should be in the same namespace as the `badlywrapped` type.
template <class T> bool lexical_cast(const std::string &input, badlywrapped<T> &output) {
// This using declaration lets us use an unqualified call to lexical_cast below. This is important because
// unqualified call finds the proper overload via argument-dependent lookup, and thus it will be able to find
// an overload for `spair` type, which is not in `CLI::detail`.
using CLI::detail::lexical_cast;
T value;
if(!lexical_cast(input, value))
return false;
output.set(value);
return true;
}
TEST_CASE_METHOD(TApp, "custom_string_converter_flag", "[newparse]") {
badlywrapped<bool> val;
std::vector<badlywrapped<bool>> vals;
app.add_flag("-1", val);
app.add_flag("-2", vals);
val.set(false);
args = {"-1"};
run();
CHECK(true == val.get());
args = {"-2", "-2"};
run();
CHECK(2 == vals.size());
CHECK(true == vals[0].get());
CHECK(true == vals[1].get());
}
TEST_CASE_METHOD(TApp, "custom_string_converter_adl", "[newparse]") {
// This test checks that the lexical_cast calls route as expected.
badlywrapped<spair> val;
app.add_option("-d,--dual_string", val);
args = {"-d", "string1:string2"};
run();
CHECK("string1" == val.get().first);
CHECK("string2" == val.get().second);
}
/// Another wrapper to test that specializing CLI::detail::lexical_cast works
struct anotherstring {
anotherstring() = default;
std::string s{};
};
// This is a custom converter done via specializing the CLI::detail::lexical_cast template. This was the recommended
// mechanism for extending the library before, so we need to test it. Don't do this in your code, use
// argument-dependent lookup as outlined in the examples for spair and template badlywrapped.
namespace CLI {
namespace detail {
template <> bool lexical_cast<anotherstring>(const std::string &input, anotherstring &output) {
bool result = lexical_cast(input, output.s);
if(result)
output.s += "!";
return result;
}
} // namespace detail
} // namespace CLI
TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") {
anotherstring s;
app.add_option("-s", s);
args = {"-s", "something"};
run();
CHECK("something!" == s.s);
}
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments /// option assignments
template <class X> class objWrapper { template <class X> class objWrapper {