diff --git a/README.md b/README.md index 22d15069c..ceae57561 100644 --- a/README.md +++ b/README.md @@ -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(option_name, @@ -202,7 +202,7 @@ app.add_option_function(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(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(...` function will typically require the template double val app.add_option("-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; 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, optional, and optional 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 v1; +app.add_option,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: diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 2466f5281..7974e061f 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -468,35 +468,37 @@ class App { /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) - template ::value && !std::is_const::value, detail::enabler> = detail::dummy> + template ::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(res[0], variable); + return detail::lexical_conversion(res, variable); }; Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { - return std::string(CLI::detail::checked_to_string(variable)); + return CLI::detail::checked_to_string(variable); }); opt->type_name(detail::type_name()); - + // 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::value; + auto XCcount = detail::type_count::value; + opt->type_size((std::max)(Tcount, XCcount)); return opt; } /// Add option for a callback of a specific type - template ::value, detail::enabler> = detail::dummy> + template Option *add_option_function(std::string option_name, const std::function &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(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()); + opt->type_size(detail::type_count::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 - Option *add_option(std::string option_name, - std::vector &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 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())->type_size(-1); - - return opt; - } - - /// Add option for a vector callback of a specific type - template ::value, detail::enabler> = detail::dummy> - Option *add_option_function(std::string option_name, - const std::function &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())->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 diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index f51a7d8a3..b8ea09ae9 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -142,7 +142,7 @@ template class is_direct_constructible { template static auto test(...) -> std::false_type; public: - static const bool value = decltype(test(0))::value; + static constexpr bool value = decltype(test(0))::value; }; #ifdef __GNUC__ #pragma GCC diagnostic pop @@ -158,7 +158,7 @@ template class is_ostreamable { template static auto test(...) -> std::false_type; public: - static const bool value = decltype(test(0))::value; + static constexpr bool value = decltype(test(0))::value; }; /// Check for input streamability @@ -169,7 +169,7 @@ template class is_istreamable { template static auto test(...) -> std::false_type; public: - static const bool value = decltype(test(0))::value; + static constexpr bool value = decltype(test(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 class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + /// Convert an object to a string (directly forward if this can become a string) template ::value, detail::enabler> = detail::dummy> auto to_string(T &&value) -> decltype(std::forward(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 ::value && !is_ostreamable::value, detail::enabler> = - detail::dummy> + enable_if_t::value && !is_ostreamable::value && + !is_vector::type>::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_vector::type>::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) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return std::string("[" + detail::join(defaults) + "]"); +} + /// special template overload template struct type_count { static const int value{0}; }; + +/// Set of overloads to get the type size of an object +template struct type_count::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; +/// Type size for regular object types that do not look like a tuple +template +struct type_count< + T, + typename std::enable_if::value && !is_tuple_like::value && !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// Type size of types that look like a vector +template struct type_count::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 struct uncommon_type { !std::is_enum::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 -struct classify_object::value && is_direct_constructible::value && - is_direct_constructible::value>::type> { +struct classify_object< + T, + typename std::enable_if::value && is_direct_constructible::value && + is_direct_constructible::value && type_count::value == 1>::type> { static constexpr objCategory value{number_constructible}; }; /// Assignable from int template -struct classify_object::value && !is_direct_constructible::value && - is_direct_constructible::value>::type> { - static const objCategory value{integer_constructible}; +struct classify_object< + T, + typename std::enable_if::value && !is_direct_constructible::value && + is_direct_constructible::value && type_count::value == 1>::type> { + static constexpr objCategory value{integer_constructible}; }; /// Assignable from double template -struct classify_object::value && is_direct_constructible::value && - !is_direct_constructible::value>::type> { - static const objCategory value{double_constructible}; +struct classify_object< + T, + typename std::enable_if::value && is_direct_constructible::value && + !is_direct_constructible::value && type_count::value == 1>::type> { + static constexpr objCategory value{double_constructible}; }; -/// vector type +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if<(is_tuple_like::value && uncommon_type::value && + !is_direct_constructible::value && !is_direct_constructible::value) || + type_count::value >= 2>::type> { + static constexpr objCategory value{tuple_value}; +}; + +/// Vector type template struct classify_object::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 ::value == vector_value, detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "VECTOR"; -} /// Print name for enumeration types template ::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 ::value == vector_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return type_name(); +} + +/// Print name for tuple types +template < + typename T, + enable_if_t::value == tuple_value && type_count::value == 1, detail::enabler> = detail::dummy> +std::string type_name() { + return type_name::type>(); +} +/// Print type name for 2 element tuples +template < + typename T, + enable_if_t::value == tuple_value && type_count::value == 2, detail::enabler> = detail::dummy> +std::string type_name() { + return std::string("[") + type_name::type>() + "," + + type_name::type>() + "]"; +} + +/// Print type name for 3 element tuples +template < + typename T, + enable_if_t::value == tuple_value && type_count::value == 3, detail::enabler> = detail::dummy> +std::string type_name() { + return std::string("[") + type_name::type>() + "," + + type_name::type>() + "," + + type_name::type>() + "]"; +} + +/// Print type name for 4 element tuples +template < + typename T, + enable_if_t::value == tuple_value && type_count::value == 4, detail::enabler> = detail::dummy> +std::string type_name() { + return std::string("[") + type_name::type>() + "," + + type_name::type>() + "," + + type_name::type>() + "," + + type_name::type>() + "]"; +} + +/// Print type name for 5 element tuples +template < + typename T, + enable_if_t::value == tuple_value && type_count::value == 5, detail::enabler> = detail::dummy> +std::string type_name() { + return std::string("[") + type_name::type>() + "," + + type_name::type>() + "," + + type_name::type>() + "," + + type_name::type>() + "," + + type_name::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 ::value, detail::enabler> = detail::dummy> +template ::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::value && std::is_assignable::value, detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, T &output) { XC val; - auto parse_result = lexical_cast(input, val); + bool parse_result = lexical_cast(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 ::value && !std::is_assignable::value && std::is_move_assignable::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 ::value && !is_tuple_like::value && !is_vector::value, detail::enabler> = + detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor +template ::value == 1 && type_count::value == 2, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &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 ::value == -1 && type_count::value == -1, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval &= lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value == -1) && (type_count::value == 1), detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval &= lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +/// Conversion for single element tuple and single element tuple conversion type +template ::value == 1 && is_tuple_like::value && is_tuple_like::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + static_assert(type_count::value == type_count::value, + "when using converting to tuples different cross conversion are not possible"); + + bool retval = lexical_assign::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 ::value == 1 && is_tuple_like::value && !is_tuple_like::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + static_assert(type_count::value == type_count::value, + "when using converting to tuples different cross conversion are not possible"); + + bool retval = lexical_assign::type, XC>(strings[0], std::get<0>(output)); + return retval; +} + +/// conversion for two element tuple +template ::value == 2, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + static_assert(type_count::value == type_count::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 ::value == 3, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + static_assert(type_count::value == type_count::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 ::value == 4, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + static_assert(type_count::value == type_count::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 ::value == 5, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + static_assert(type_count::value == type_count::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 diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp index 5495cccf0..80a663222 100644 --- a/tests/HelpersTest.cpp +++ b/tests/HelpersTest.cpp @@ -1,11 +1,13 @@ #include "app_helper.hpp" +#include #include #include #include #include #include #include +#include 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::value); + EXPECT_FALSE(CLI::detail::is_tuple_like>::value); + auto v = CLI::detail::is_tuple_like>::value; + EXPECT_TRUE(v); + v = CLI::detail::is_tuple_like>::value; + EXPECT_TRUE(v); +} + +TEST(TypeTools, type_size) { + auto V = CLI::detail::type_count::value; + EXPECT_EQ(V, 1); + V = CLI::detail::type_count::value; + EXPECT_EQ(V, 0); + V = CLI::detail::type_count>::value; + EXPECT_EQ(V, -1); + V = CLI::detail::type_count>::value; + EXPECT_EQ(V, 2); + V = CLI::detail::type_count>::value; + EXPECT_EQ(V, 3); + V = CLI::detail::type_count>::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>(); - EXPECT_EQ("VECTOR", vector_name); + EXPECT_EQ("INT", vector_name); + + vector_name = CLI::detail::type_name>(); + EXPECT_EQ("FLOAT", vector_name); + + vector_name = CLI::detail::type_name>>(); + EXPECT_EQ("UINT", vector_name); + auto vclass = CLI::detail::classify_object>::value; + EXPECT_EQ(vclass, CLI::detail::objCategory::number_constructible); + + std::string tuple_name = CLI::detail::type_name>(); + EXPECT_EQ("FLOAT", tuple_name); + + static_assert(CLI::detail::classify_object>::value == + CLI::detail::objCategory::tuple_value, + "tuple does not read like a tuple"); + tuple_name = CLI::detail::type_name>(); + EXPECT_EQ("[INT,TEXT]", tuple_name); + + tuple_name = CLI::detail::type_name>(); + EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name); + + tuple_name = CLI::detail::type_name>(); + EXPECT_EQ("[INT,TEXT,FLOAT,UINT]", tuple_name); + + tuple_name = CLI::detail::type_name>(); + EXPECT_EQ("[INT,TEXT,FLOAT,UINT,TEXT]", tuple_name); std::string text_name = CLI::detail::type_name(); 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(); EXPECT_EQ("ENUM", enum_name); + + vclass = CLI::detail::classify_object>::value; + EXPECT_EQ(vclass, CLI::detail::objCategory::tuple_value); + static_assert(CLI::detail::classify_object>::value == CLI::detail::objCategory::tuple_value, + "tuple does not classify as a tuple"); + std::string enum_name2 = CLI::detail::type_name>(); + 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(input, x); + EXPECT_TRUE(res); + EXPECT_FLOAT_EQ((float)9.12, (float)x); + + CLI::results_t bad_input = {"hello"}; + res = CLI::detail::lexical_conversion(input, x); + EXPECT_TRUE(res); +} + +TEST(Types, LexicalConversionDoubleTuple) { + CLI::results_t input = {"9.12"}; + std::tuple x; + bool res = CLI::detail::lexical_conversion(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(input, x); + EXPECT_TRUE(res); +} + +TEST(Types, LexicalConversionVectorDouble) { + CLI::results_t input = {"9.12", "10.79", "-3.54"}; + std::vector x; + bool res = CLI::detail::lexical_conversion, 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>(input, x); + EXPECT_TRUE(res); + EXPECT_EQ(x.size(), 3u); + EXPECT_DOUBLE_EQ(x[2], -3.54); +} + +static_assert(!CLI::detail::is_tuple_like>::value, "vector should not be like a tuple"); +static_assert(CLI::detail::is_tuple_like>::value, "pair of double should be like a tuple"); +static_assert(CLI::detail::is_tuple_like>::value, "std::array should be like a tuple"); +static_assert(!CLI::detail::is_tuple_like::value, "std::string should not be like a tuple"); +static_assert(!CLI::detail::is_tuple_like::value, "double should not be like a tuple"); +static_assert(CLI::detail::is_tuple_like>::value, "tuple should look like a tuple"); + +TEST(Types, LexicalConversionTuple2) { + CLI::results_t input = {"9.12", "19"}; + + std::tuple x; + static_assert(CLI::detail::is_tuple_like::value, + "tuple type must have is_tuple_like trait to be true"); + bool res = CLI::detail::lexical_conversion(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(input, x); + EXPECT_FALSE(res); +} + +TEST(Types, LexicalConversionTuple3) { + CLI::results_t input = {"9.12", "19", "hippo"}; + std::tuple x; + bool res = CLI::detail::lexical_conversion(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(input, x); + EXPECT_FALSE(res); +} + +TEST(Types, LexicalConversionTuple4) { + CLI::results_t input = {"9.12", "19", "18.6", "5.87"}; + std::array x; + bool res = CLI::detail::lexical_conversion(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(input, x); + EXPECT_FALSE(res); +} + +TEST(Types, LexicalConversionTuple5) { + CLI::results_t input = {"9", "19", "18", "5", "235235"}; + std::array x; + bool res = CLI::detail::lexical_conversion(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(input, x); + EXPECT_FALSE(res); +} + +TEST(Types, LexicalConversionXomplwz) { + CLI::results_t input = {"5.1", "3.5"}; + std::complex x; + bool res = CLI::detail::lexical_conversion, std::array>(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"; diff --git a/tests/NewParseTest.cpp b/tests/NewParseTest.cpp index dd5b8265d..4a3997af5 100644 --- a/tests/NewParseTest.cpp +++ b/tests/NewParseTest.cpp @@ -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>(const std::string &input, - std::pair &output) { +template <> bool lexical_cast(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>(const std::string &input, } // namespace CLI TEST_F(TApp, custom_string_converter) { - std::pair 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 val; + spair val; app.add_option("-d,--dual_string", val); args = {"-d", "string2"};