From a5733080690b2246de83531add1a2b0367a88918 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 10 Feb 2020 07:46:55 -0800 Subject: [PATCH] try to work through some issues with complex and other containers. --- include/CLI/App.hpp | 23 ++++- include/CLI/TypeTools.hpp | 178 ++++++++++++++++++++++++++++---------- tests/HelpersTest.cpp | 16 ++-- tests/NewParseTest.cpp | 1 + tests/OptionTypeTest.cpp | 8 +- tests/OptionalTest.cpp | 9 +- 6 files changed, 179 insertions(+), 56 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 3622da957..39dcdaa42 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -613,6 +613,27 @@ class App { return opt; } + /// Add option for assigning to a variable + template ::value, detail::enabler> = detail::dummy> + Option *add_option_no_stream(std::string option_name, + AssignTo &variable, ///< The variable to set + std::string option_description = "") { + + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing + return detail::lexical_conversion(res, variable); + }; + + Option *opt = add_option(option_name, fun, option_description, false, []() { + return std::string{}; + }); + opt->type_name(detail::type_name()); + opt->type_size(detail::type_count::value); + opt->expected(detail::expected_count::value); + opt->run_callback_for_default(); + return opt; + } + /// Add option for a callback of a specific type template Option *add_option_function(std::string option_name, @@ -746,7 +767,7 @@ class App { /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes /// that can be converted from a string template ::value && !std::is_const::value && + enable_if_t::value && !std::is_const::value && (!std::is_integral::value || is_bool::value) && !std::is_constructible, T>::value, detail::enabler> = detail::dummy> diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 6896356a5..dfb6dbb7f 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -183,6 +183,17 @@ template class is_istreamable { static constexpr bool value = decltype(test(0))::value; }; + /// Check for complex + template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...)->std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; + }; + /// Templated operation to get a value from a stream template ::value, detail::enabler> = detail::dummy> bool from_stream(const std::string &istring, T &obj) { @@ -197,14 +208,14 @@ bool from_stream(const std::string & /*istring*/, T & /*obj*/) { return false; } -// check to see if an object is a container (fail by default) -template struct is_container : std::false_type {}; +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; /// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an en end /// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from /// a std::string template -struct is_container< +struct is_mutable_container< T, conditional_t> : public conditional_t::value, std::false_type, std::true_type> {}; +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an en end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), + decltype(std::declval().begin())>, + void>> + : public std::true_type{}; + + // check to see if an object is a wrapper (fail by default) template struct is_wrapper : std::false_type {}; @@ -261,22 +288,23 @@ std::string to_string(T &&value) { } /// If conversion is not supported, return an empty string (streaming is not supported for that type) -template ::value && !is_ostreamable::value && - !is_container::type>::type>::value, - detail::enabler> = detail::dummy> +template < + typename T, + enable_if_t::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> std::string to_string(T &&) { return std::string{}; } /// convert a vector to a string -template ::value && !is_ostreamable::value && - is_container::type>::type>::value, - detail::enabler> = detail::dummy> +template < + typename T, + enable_if_t::value && !is_ostreamable::value && + is_readable_container::type>::value, + detail::enabler> = detail::dummy> std::string to_string(T &&variable) { std::vector defaults; - defaults.reserve(variable.size()); auto cval = variable.begin(); auto end = variable.end(); while(cval != end) { @@ -330,21 +358,21 @@ template struct type_count struct type_count::value && !is_wrapper::value && + typename std::enable_if::value && !is_wrapper::value && !is_tuple_like::value && !std::is_void::value>::type> { static constexpr int value{1}; }; /// Type size of types that look like a container -template struct type_count::value>::type> { - static constexpr int value{is_container::value ? expected_max_vector_size +template struct type_count::value>::type> { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; }; /// Type size for wrapper type that are not containers template struct type_count::value && is_wrapper::value && !is_tuple_like::value && + typename std::enable_if::value && is_wrapper::value && !is_tuple_like::value && !std::is_void::value>::type> { static constexpr int value{type_count::value}; }; @@ -354,11 +382,11 @@ template struct expected_count { static con /// For most types the number of expected items is 1 template -struct expected_count::value && !std::is_void::value>::type> { +struct expected_count::value && !std::is_void::value>::type> { static constexpr int value{1}; }; /// number of expected items in a vector -template struct expected_count::value>::type> { +template struct expected_count::value>::type> { static constexpr int value{expected_max_vector_size}; }; @@ -372,6 +400,7 @@ enum class object_category : int { number_constructible = 12, double_constructible = 14, integer_constructible = 16, + complex_number=22, tuple_value = 35, wrapper_value = 39, container_value = 40, @@ -435,13 +464,17 @@ template struct classify_object struct classify_object::value>::type> { + static constexpr object_category value{ object_category::complex_number }; +}; + /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, /// vectors, and enumerations template struct uncommon_type { using type = typename std::conditional::value && !std::is_integral::value && !std::is_assignable::value && - !std::is_constructible::value && - !is_container::value && !std::is_enum::value, + !std::is_constructible::value && !is_complex::value && + !is_mutable_container::value && !std::is_enum::value, std::true_type, std::false_type>::type; static constexpr bool value = type::value; @@ -449,36 +482,35 @@ template struct uncommon_type { /// wrapper type template -struct classify_object< - T, - typename std::enable_if<(!is_container::value && is_wrapper::value && !is_tuple_like::value && - uncommon_type::value)>::type> { - static constexpr object_category value{ object_category::wrapper_value }; +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; }; /// Assignable from double or int template struct classify_object::value && type_count::value == 1 && !is_wrapper::value && - is_direct_constructible::value && - is_direct_constructible::value>::type> { - static constexpr object_category value{ object_category::number_constructible }; + typename std::enable_if::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; }; /// Assignable from int template struct classify_object::value && type_count::value == 1 && !is_wrapper::value && - !is_direct_constructible::value && - is_direct_constructible::value>::type> { - static constexpr object_category value{ object_category::integer_constructible }; + typename std::enable_if::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; }; /// Assignable from double template struct classify_object::value && type_count::value == 1 && !is_wrapper::value && - is_direct_constructible::value && + typename std::enable_if::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && !is_direct_constructible::value>::type> { static constexpr object_category value{object_category::double_constructible}; }; @@ -486,7 +518,7 @@ struct classify_object struct classify_object::value >= 2 && !is_wrapper::value) || + typename std::enable_if<(type_count::value >= 2 && !is_wrapper::value ) || (is_tuple_like::value && uncommon_type::value && !is_direct_constructible::value && !is_direct_constructible::value)>::type> { @@ -494,7 +526,7 @@ struct classify_object struct classify_object::value>::type> { +template struct classify_object::value>::type> { static constexpr object_category value{object_category::container_value}; }; @@ -541,6 +573,13 @@ constexpr const char *type_name() { return "BOOLEAN"; } +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> + constexpr const char *type_name() { + return "COMPLEX"; +} + /// Print for all other types template ::value >= object_category::string_assignable, detail::enabler> = detail::dummy> @@ -703,6 +742,41 @@ bool lexical_cast(const std::string &input, T &output) { } } +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string &input, T &output) { + using XC=typename conditional_t::value, typename T::value_type, double>; + XC x, y; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of('-'); + if (nloc != std::string::npos && nloc > 0) { + worked = detail::lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if (str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && detail::lexical_cast(str1, y); + } + else { + if (str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = detail::lexical_cast(str1, y); + x = XC{ 0 }; + } + else { + worked = detail::lexical_cast(str1, x); + y = XC{ 0 }; + } + } + if (worked) + { + output = T{ x,y }; + return worked; + } + return from_stream(input, output); +} + /// String and similar direct assignment template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> @@ -735,12 +809,11 @@ bool lexical_cast(const std::string &input, T &output) { /// wrapper types template ::value == object_category::wrapper_value, detail::enabler> = detail::dummy> - bool lexical_cast(const std::string &input, T &output) { + enable_if_t::value == object_category::wrapper_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { typename T::value_type val; - if (lexical_cast(input, val)) - { - output = T{ val }; + if(lexical_cast(input, val)) { + output = T{val}; return true; } return from_stream(input, output); @@ -856,8 +929,9 @@ bool lexical_assign(const std::string &input, T &output) { /// Lexical conversion if there is only one element template ::value && !is_tuple_like::value && !is_container::value && - !is_container::value, + enable_if_t::value && !is_tuple_like::value && !is_mutable_container::value && + !(classify_object::value == object_category::wrapper_value || + classify_object::value == object_category::container_value), detail::enabler> = detail::dummy> bool lexical_conversion(const std::vector &strings, T &output) { return lexical_assign(strings[0], output); @@ -880,6 +954,20 @@ bool lexical_conversion(const std::vector &strings, T &output) { return retval; } +/// wrapper types +template ::value == object_category::wrapper_value && type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + typename XC::value_type val; + if(lexical_conversion(strings, val)) { + output = XC{val}; + return true; + } + return false; +} + /// Lexical conversion of a vector types template &strings, T &output) { /// Lexical conversion if there is only one element but the conversion type is a vector template ::value && !is_container::value && is_container::value, detail::enabler> = + enable_if_t::value && !is_mutable_container::value && is_mutable_container::value, detail::enabler> = detail::dummy> bool lexical_conversion(const std::vector &strings, T &output) { diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp index 8dc64e700..7e7e84aa7 100644 --- a/tests/HelpersTest.cpp +++ b/tests/HelpersTest.cpp @@ -1178,11 +1178,17 @@ static_assert(CLI::detail::is_wrapper>::value, "vector static_assert(CLI::detail::is_wrapper::value, "string should be a wrapper"); static_assert(!CLI::detail::is_wrapper::value, "double should not be a wrapper"); -static_assert(CLI::detail::is_container>::value, "vector class should be a container"); -static_assert(CLI::detail::is_container>::value, "vector class should be a container"); -static_assert(!CLI::detail::is_container::value, "string should be a container"); -static_assert(!CLI::detail::is_container::value, "double should not be a container"); -static_assert(!CLI::detail::is_container>::value, "array should not be a container"); +static_assert(CLI::detail::is_mutable_container>::value, "vector class should be a container"); +static_assert(CLI::detail::is_mutable_container>::value, "vector class should be a container"); +static_assert(!CLI::detail::is_mutable_container::value, "string should be a container"); +static_assert(!CLI::detail::is_mutable_container::value, "double should not be a container"); +static_assert(!CLI::detail::is_mutable_container>::value, "array should not be a container"); + +static_assert(CLI::detail::is_mutable_container>::value, "vector int should be a container"); + +static_assert(CLI::detail::is_readable_container &>::value, "vector int & should be a readable container"); +static_assert(CLI::detail::is_readable_container>::value, "const vector int should be a readable container"); +static_assert(CLI::detail::is_readable_container &>::value, "const vector int & should be a readable container"); TEST(FixNewLines, BasicCheck) { std::string input = "one\ntwo"; diff --git a/tests/NewParseTest.cpp b/tests/NewParseTest.cpp index 6d7a48733..8e9e62c1e 100644 --- a/tests/NewParseTest.cpp +++ b/tests/NewParseTest.cpp @@ -318,6 +318,7 @@ TEST_F(TApp, AddingComplexParserDetail) { } catch(...) { skip_tests = true; } + static_assert(CLI::detail::is_complex::value, "complex should register as complex in this situation"); if(!skip_tests) { cx comp{0, 0}; app.add_option("-c,--complex", comp, "add a complex number option"); diff --git a/tests/OptionTypeTest.cpp b/tests/OptionTypeTest.cpp index 55a353b7e..18b27a37e 100644 --- a/tests/OptionTypeTest.cpp +++ b/tests/OptionTypeTest.cpp @@ -565,12 +565,12 @@ TEST_F(TApp, vectorVector) { EXPECT_THROW(run(), CLI::ConversionError); } -static_assert(CLI::detail::is_container>::value, "set should be a container"); -static_assert(CLI::detail::is_container>::value, "map should be a container"); -static_assert(CLI::detail::is_container>::value, +static_assert(CLI::detail::is_mutable_container>::value, "set should be a container"); +static_assert(CLI::detail::is_mutable_container>::value, "map should be a container"); +static_assert(CLI::detail::is_mutable_container>::value, "unordered_map should be a container"); -static_assert(CLI::detail::is_container>>::value, "list should be a container"); +static_assert(CLI::detail::is_mutable_container>>::value, "list should be a container"); static_assert(CLI::detail::type_count>::value == 1, "set should have a type size of 1"); static_assert(CLI::detail::type_count>>::value == 3, diff --git a/tests/OptionalTest.cpp b/tests/OptionalTest.cpp index f325db614..47a9f5a7d 100644 --- a/tests/OptionalTest.cpp +++ b/tests/OptionalTest.cpp @@ -165,12 +165,18 @@ TEST_F(TApp, BoostOptionalStringTest) { EXPECT_TRUE(opt); EXPECT_EQ(*opt, "strv"); } +namespace boost +{ + using CLI::enums::operator<<; +} + TEST_F(TApp, BoostOptionalEnumTest) { + enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 }; boost::optional opt,opt2; auto optptr = app.add_option("-v,--val", opt); - // app.add_option("-e,--eval", opt2); + app.add_option_no_stream("-e,--eval", opt2); optptr->capture_default_str(); auto dstring = optptr->get_default_str(); @@ -187,6 +193,7 @@ TEST_F(TApp, BoostOptionalEnumTest) { run(); EXPECT_TRUE(opt); EXPECT_TRUE(*opt == eval::val1); + } TEST_F(TApp, BoostOptionalVector) {