diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fdff12d..f1f52fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Note: This is the final release with `requires`, please switch to `needs`. * Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95] * All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95] * The internal stream was not being cleared before use in some cases. Fixed. [#95] +* Using an emum now requires explicit conversion overload [#97] Other, non-user facing changes: @@ -24,6 +25,7 @@ Other, non-user facing changes: [#90]: https://github.com/CLIUtils/CLI11/issues/90 [#92]: https://github.com/CLIUtils/CLI11/issues/92 [#95]: https://github.com/CLIUtils/CLI11/pull/95 +[#97]: https://github.com/CLIUtils/CLI11/pull/97 ## Version 1.4: More feedback diff --git a/README.md b/README.md index 4d3bd121..437208ae 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,11 @@ Also, in a related note, the `App` you get a pointer to is stored in the parent ## How it works Every `add_` option you have seen so far depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of strings, so it knows how many times an option was passed or how many arguments it received (flags add empty strings to keep the counts correct). The lambda returns `true` if it could validate the option strings, and -`false` if it failed. If you wanted to extend this to support a new type, just use a lambda. An example of a new parser for `complex` that supports all of the features of a standard `add_options` call is in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: +`false` if it failed. + +Other values can be added as long as they support `operator>>` (and defaults can be printed if they support `operator<<`). To add an enum, for example, provide a custom `operator>>` with an `istream` (inside the CLI namespace is fine if you don't want to interfere with an existing `operator>>`). + +If you wanted to extend this to support a completely new type, just use a lambda. An example of a new parser for `complex` that supports all of the features of a standard `add_options` call is in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: ### Example diff --git a/cmake/AddGoogletest.cmake b/cmake/AddGoogletest.cmake index 0c035d2d..e6bf162a 100644 --- a/cmake/AddGoogletest.cmake +++ b/cmake/AddGoogletest.cmake @@ -4,7 +4,7 @@ # gives output on failed tests without having to set an environment variable. # # -set(gtest_force_shared_crt CACHE INTERNAL ON) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) set(BUILD_SHARED_LIBS OFF) if(CMAKE_VERSION VERSION_LESS 3.11) diff --git a/examples/enum.cpp b/examples/enum.cpp index 827bee39..7018681f 100644 --- a/examples/enum.cpp +++ b/examples/enum.cpp @@ -1,12 +1,22 @@ +#include #include -enum Level : std::int32_t { High, Medium, Low }; +enum class Level : int { High, Medium, Low }; + +std::istream &operator>>(std::istream &in, Level &level) { + int i; + in >> i; + level = static_cast(i); + return in; +} + +std::ostream &operator<<(std::ostream &in, const Level &level) { return in << static_cast(level); } int main(int argc, char **argv) { CLI::App app; Level level; - app.add_set("-l,--level", level, {High, Medium, Low}, "Level settings") + app.add_set("-l,--level", level, {Level::High, Level::Medium, Level::Low}, "Level settings") ->set_type_name("enum/Level in {High=0, Medium=1, Low=2}"); CLI11_PARSE(app, argc, argv); diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 80d72622..aea4b856 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -74,8 +74,7 @@ constexpr const char *type_name() { /// Signed integers / enums template ::value && std::is_signed::value) || std::is_enum::value, - detail::enabler> = detail::dummy> + enable_if_t<(std::is_integral::value && std::is_signed::value), detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { size_t n = 0; @@ -124,7 +123,7 @@ bool lexical_cast(std::string input, T &output) { /// String and similar template ::value && !std::is_integral::value && !std::is_enum::value && + enable_if_t::value && !std::is_integral::value && std::is_assignable::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { @@ -134,7 +133,7 @@ bool lexical_cast(std::string input, T &output) { /// Non-string parsable template ::value && !std::is_integral::value && !std::is_enum::value && + enable_if_t::value && !std::is_integral::value && !std::is_assignable::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 85d7e653..0b1c2416 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -321,8 +321,8 @@ TEST_F(TApp, ComplexOptMulti) { run(); - EXPECT_FLOAT_EQ(val.real(), 1); - EXPECT_FLOAT_EQ(val.imag(), 2); + EXPECT_DOUBLE_EQ(val.real(), 1); + EXPECT_DOUBLE_EQ(val.imag(), 2); } TEST_F(TApp, MissingValueNonRequiredOpt) { @@ -643,26 +643,6 @@ TEST_F(TApp, NotRequiedExpectedDoubleShort) { EXPECT_THROW(run(), CLI::ArgumentMismatch); } -TEST_F(TApp, EnumTest) { - enum Level : std::int32_t { High, Medium, Low }; - Level level = Level::Low; - app.add_option("--level", level); - - args = {"--level", "1"}; - run(); - EXPECT_EQ(level, Level::Medium); -} - -TEST_F(TApp, NewEnumTest) { - enum class Level2 : std::int32_t { High, Medium, Low }; - Level2 level = Level2::Low; - app.add_option("--level", level); - - args = {"--level", "1"}; - run(); - EXPECT_EQ(level, Level2::Medium); -} - TEST_F(TApp, RequiredFlags) { app.add_flag("-a")->required(); app.add_flag("-b")->mandatory(); // Alternate term