diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 3ef5a104..8fd85e47 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -575,6 +575,8 @@ class App { std::string simple_name = CLI::detail::split(name, ',').at(0); CLI::callback_t fun = [&variable, simple_name, label](results_t res) { + if(res[1].back() == 'i') + res[1].pop_back(); double x, y; bool worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(res[1], y); if(worked) diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 7d1909ea..58476d9f 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -72,13 +72,33 @@ constexpr const char *type_name() { // Lexical cast -/// Integers / enums +/// Signed integers / enums template ::value || std::is_enum::value, detail::enabler> = detail::dummy> + enable_if_t<(std::is_integral::value && std::is_signed::value) || std::is_enum::value, + detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { - output = static_cast(std::stoll(input)); - return true; + size_t n = 0; + output = static_cast(std::stoll(input, &n, 0)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + size_t n = 0; + output = static_cast(std::stoull(input, &n, 0)); + return n == input.size(); } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { @@ -90,8 +110,9 @@ bool lexical_cast(std::string input, T &output) { template ::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { - output = static_cast(std::stold(input)); - return true; + size_t n = 0; + output = static_cast(std::stold(input, &n)); + return n == input.size(); } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { @@ -101,12 +122,33 @@ 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_enum::value && + std::is_assignable::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { output = input; return true; } +/// Non-string parsable +template ::value && !std::is_integral::value && !std::is_enum::value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + +// On GCC 4.7, thread_local is not available, so this optimization +// is turned off (avoiding multiple initialisations on multiple usages +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && __GNUC__ == 4 && (__GNUC_MINOR__ < 8) + std::istringstream is; +#else + static thread_local std::istringstream is; +#endif + + is.str(input); + is >> output; + return !is.fail() && !is.rdbuf()->in_avail(); +} + } // namespace detail } // namespace CLI diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp index b1fb343d..0660dacc 100644 --- a/tests/HelpersTest.cpp +++ b/tests/HelpersTest.cpp @@ -4,6 +4,7 @@ #include #include #include +#include TEST(Split, SimpleByToken) { auto out = CLI::detail::split("one.two.three", '.'); @@ -348,17 +349,30 @@ TEST(Types, TypeName) { } TEST(Types, LexicalCastInt) { - std::string input = "912"; - int x; - EXPECT_TRUE(CLI::detail::lexical_cast(input, x)); - EXPECT_EQ(912, x); + std::string signed_input = "-912"; + int x_signed; + EXPECT_TRUE(CLI::detail::lexical_cast(signed_input, x_signed)); + EXPECT_EQ(-912, x_signed); + + std::string unsigned_input = "912"; + unsigned int x_unsigned; + EXPECT_TRUE(CLI::detail::lexical_cast(unsigned_input, x_unsigned)); + EXPECT_EQ(912, x_unsigned); + + EXPECT_FALSE(CLI::detail::lexical_cast(signed_input, x_unsigned)); unsigned char y; std::string overflow_input = std::to_string(UINT64_MAX) + "0"; EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, y)); + char y_signed; + EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, y_signed)); + std::string bad_input = "hello"; EXPECT_FALSE(CLI::detail::lexical_cast(bad_input, y)); + + std::string extra_input = "912i"; + EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, y)); } TEST(Types, LexicalCastDouble) { @@ -372,6 +386,9 @@ TEST(Types, LexicalCastDouble) { std::string overflow_input = "1" + std::to_string(LDBL_MAX); EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, x)); + + std::string extra_input = "9.12i"; + EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, x)); } TEST(Types, LexicalCastString) { @@ -381,6 +398,20 @@ TEST(Types, LexicalCastString) { EXPECT_EQ(input, output); } +TEST(Types, LexicalCastParsable) { + std::string input = "(4.2,7.3)"; + std::string fail_input = "4.2,7.3"; + std::string extra_input = "(4.2,7.3)e"; + + std::complex output; + EXPECT_TRUE(CLI::detail::lexical_cast(input, output)); + EXPECT_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble + EXPECT_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const + + EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); + EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); +} + TEST(FixNewLines, BasicCheck) { std::string input = "one\ntwo"; std::string output = "one\n; two";