From 9bb553b882c95f0dc743cdf8501f70f7a3102d43 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Fri, 27 Oct 2023 09:16:25 -0500 Subject: [PATCH 1/4] #277 Added in-built support for string_type choices --- include/argparse/argparse.hpp | 123 ++++++++++++++++++++++++++++----- test/CMakeLists.txt | 1 + test/test_append.cpp | 2 +- test/test_as_container.cpp | 7 +- test/test_bool_operator.cpp | 7 +- test/test_choices.cpp | 70 +++++++++++++++++++ test/test_default_args.cpp | 4 +- test/test_equals_form.cpp | 2 +- test/test_get.cpp | 2 +- test/test_help.cpp | 26 ++++--- test/test_parse_known_args.cpp | 2 +- test/test_repr.cpp | 2 +- test/test_subparsers.cpp | 6 +- 13 files changed, 204 insertions(+), 50 deletions(-) create mode 100644 test/test_choices.cpp diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 588666c..f54d4c3 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -428,7 +428,7 @@ public: } template - auto action(F &&callable, Args &&... bound_args) + auto action(F &&callable, Args &&...bound_args) -> std::enable_if_t, Argument &> { using action_type = std::conditional_t< @@ -509,10 +509,12 @@ public: m_num_args_range = NArgsRange{0, 1}; break; case nargs_pattern::any: - m_num_args_range = NArgsRange{0, (std::numeric_limits::max)()}; + m_num_args_range = + NArgsRange{0, (std::numeric_limits::max)()}; break; case nargs_pattern::at_least_one: - m_num_args_range = NArgsRange{1, (std::numeric_limits::max)()}; + m_num_args_range = + NArgsRange{1, (std::numeric_limits::max)()}; break; } return *this; @@ -523,6 +525,80 @@ public: return nargs(nargs_pattern::any); } + void add_choice(const std::string &choice) { + if (!m_choices.has_value()) { + /// create it + m_choices = std::vector{}; + } + m_choices.value().push_back(choice); + } + + Argument &choices() { + if (!m_choices.has_value()) { + throw std::runtime_error("Zero choices provided"); + } + return *this; + } + + template + Argument &choices(const std::string &first, T &...rest) { + add_choice(first); + choices(rest...); + + return *this; + } + + template Argument &choices(const char *first, T &...rest) { + add_choice(first); + choices(rest...); + + return *this; + } + + void find_default_value_in_choices_or_throw() const { + + const auto &choices = m_choices.value(); + + if (m_default_value.has_value()) { + if (std::find(choices.begin(), choices.end(), m_default_value_repr) == + choices.end()) { + // provided arg not in list of allowed choices + // report error + + std::string choices_as_csv = + std::accumulate(choices.begin(), choices.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.empty() ? "" : ", ") + b; + }); + + throw std::runtime_error( + std::string{"Invalid default value "} + m_default_value_repr + + " - allowed options: {" + choices_as_csv + "}"); + } + } + } + + template + void find_value_in_choices_or_throw(Iterator it) const { + + const auto &choices = m_choices.value(); + + if (std::find(choices.begin(), choices.end(), *it) == choices.end()) { + // provided arg not in list of allowed choices + // report error + + std::string choices_as_csv = + std::accumulate(choices.begin(), choices.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.empty() ? "" : ", ") + b; + }); + + throw std::runtime_error(std::string{"Invalid argument "} + + details::repr(*it) + " - allowed options: {" + + choices_as_csv + "}"); + } + } + template Iterator consume(Iterator start, Iterator end, std::string_view used_name = {}) { @@ -532,6 +608,14 @@ public: m_is_used = true; m_used_name = used_name; + if (m_choices.has_value()) { + // Check each value in (start, end) and make sure + // it is in the list of allowed choices/options + for (auto it = start; it != end; ++it) { + find_value_in_choices_or_throw(it); + } + } + const auto num_args_max = m_num_args_range.get_max(); const auto num_args_min = m_num_args_range.get_min(); std::size_t dist = 0; @@ -602,6 +686,12 @@ public: throw_nargs_range_validation_error(); } } + + if (m_choices.has_value()) { + // Make sure the default value (if provided) + // is in the list of choices + find_default_value_in_choices_or_throw(); + } } std::string get_inline_usage() const { @@ -738,8 +828,7 @@ public: using ValueType = typename T::value_type; auto lhs = get(); return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), - std::end(rhs), - [](const auto &a, const auto &b) { + std::end(rhs), [](const auto &a, const auto &b) { return std::any_cast(a) == b; }); } @@ -1064,6 +1153,7 @@ private: std::any m_default_value; std::string m_default_value_repr; std::any m_implicit_value; + std::optional> m_choices{std::nullopt}; using valued_action = std::function; using void_action = std::function; std::variant m_action{ @@ -1152,16 +1242,11 @@ public: } explicit operator bool() const { - auto arg_used = std::any_of(m_argument_map.cbegin(), - m_argument_map.cend(), - [](auto &it) { - return it.second->m_is_used; - }); - auto subparser_used = std::any_of(m_subparser_used.cbegin(), - m_subparser_used.cend(), - [](auto &it) { - return it.second; - }); + auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(), + [](auto &it) { return it.second->m_is_used; }); + auto subparser_used = + std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(), + [](auto &it) { return it.second; }); return m_is_parsed && (arg_used || subparser_used); } @@ -1186,7 +1271,7 @@ public: // Parameter packed add_parents method // Accepts a variadic number of ArgumentParser objects template - ArgumentParser &add_parents(const Targs &... f_args) { + ArgumentParser &add_parents(const Targs &...f_args) { for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { for (const auto &argument : parent_parser.m_positional_arguments) { auto it = m_positional_arguments.insert( @@ -1215,8 +1300,7 @@ public: /* Getter for arguments and subparsers. * @throws std::logic_error in case of an invalid argument or subparser name */ - template - T& at(std::string_view name) { + template T &at(std::string_view name) { if constexpr (std::is_same_v) { return (*this)[name]; } else { @@ -1692,7 +1776,8 @@ private: } std::size_t max_size = 0; for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - max_size = std::max(max_size, argument->get_arguments_length()); + max_size = + std::max(max_size, argument->get_arguments_length()); } for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { max_size = std::max(max_size, command.size()); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2be4acc..87104e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,6 +29,7 @@ file(GLOB ARGPARSE_TEST_SOURCES test_append.cpp test_as_container.cpp test_bool_operator.cpp + test_choices.cpp test_compound_arguments.cpp test_container_arguments.cpp test_const_correct.cpp diff --git a/test/test_append.cpp b/test/test_append.cpp index 0114f71..8a68c65 100644 --- a/test/test_append.cpp +++ b/test/test_append.cpp @@ -5,8 +5,8 @@ import argparse; #endif #include -#include #include +#include using doctest::test_suite; diff --git a/test/test_as_container.cpp b/test/test_as_container.cpp index fb6e87c..37f725e 100644 --- a/test/test_as_container.cpp +++ b/test/test_as_container.cpp @@ -28,8 +28,8 @@ TEST_CASE("Get argument with .at()" * test_suite("as_container")) { SUBCASE("with unknown argument") { program.parse_args({"test"}); - REQUIRE_THROWS_WITH_AS(program.at("--folder"), - "No such argument: --folder", std::logic_error); + REQUIRE_THROWS_WITH_AS(program.at("--folder"), "No such argument: --folder", + std::logic_error); } } @@ -44,7 +44,8 @@ TEST_CASE("Get subparser with .at()" * test_suite("as_container")) { SUBCASE("and its argument") { program.parse_args({"test", "walk", "4km/h"}); REQUIRE(&(program.at("walk")) == &walk_cmd); - REQUIRE(&(program.at("walk").at("speed")) == &speed); + REQUIRE(&(program.at("walk").at("speed")) == + &speed); REQUIRE(program.at("walk").is_used("speed")); } diff --git a/test/test_bool_operator.cpp b/test/test_bool_operator.cpp index aada150..a34b5d6 100644 --- a/test/test_bool_operator.cpp +++ b/test/test_bool_operator.cpp @@ -8,8 +8,7 @@ import argparse; using doctest::test_suite; -TEST_CASE("ArgumentParser in bool context" * - test_suite("argument_parser")) { +TEST_CASE("ArgumentParser in bool context" * test_suite("argument_parser")) { argparse::ArgumentParser program("test"); program.add_argument("cases").remaining(); @@ -39,7 +38,7 @@ TEST_CASE("With subparsers in bool context" * test_suite("argument_parser")) { } TEST_CASE("Parsers remain false with unknown arguments" * - test_suite("argument_parser")) { + test_suite("argument_parser")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser cmd_build("build"); @@ -59,7 +58,7 @@ TEST_CASE("Parsers remain false with unknown arguments" * } TEST_CASE("Multi-level parsers match subparser bool" * - test_suite("argument_parser")) { + test_suite("argument_parser")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser cmd_cook("cook"); diff --git a/test/test_choices.cpp b/test/test_choices.cpp new file mode 100644 index 0000000..9c17d23 --- /dev/null +++ b/test/test_choices.cpp @@ -0,0 +1,70 @@ +#ifdef WITH_MODULE +import argparse; +#else +#include +#endif + +#include + +using doctest::test_suite; + +TEST_CASE("Parse argument that is provided zero choices" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + REQUIRE_THROWS_WITH_AS(program.add_argument("color").choices(), + "Zero choices provided", std::runtime_error); +} + +TEST_CASE("Parse argument that is in the fixed number of allowed choices" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + program.add_argument("color").choices("red", "green", "blue"); + + program.parse_args({"test", "red"}); +} + +TEST_CASE("Parse argument that is in the fixed number of allowed choices, with " + "invalid default" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + program.add_argument("color").default_value("yellow").choices("red", "green", + "blue"); + + REQUIRE_THROWS_WITH_AS( + program.parse_args({"test"}), + "Invalid default value \"yellow\" - allowed options: {red, green, blue}", + std::runtime_error); +} + +TEST_CASE("Parse invalid argument that is not in the fixed number of allowed " + "choices" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + program.add_argument("color").choices("red", "green", "blue"); + + REQUIRE_THROWS_WITH_AS( + program.parse_args({"test", "red2"}), + "Invalid argument \"red2\" - allowed options: {red, green, blue}", + std::runtime_error); +} + +TEST_CASE( + "Parse multiple arguments that are in the fixed number of allowed choices" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + program.add_argument("color").nargs(2).choices("red", "green", "blue"); + + program.parse_args({"test", "red", "green"}); +} + +TEST_CASE("Parse multiple arguments one of which is not in the fixed number of " + "allowed choices" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + program.add_argument("color").nargs(2).choices("red", "green", "blue"); + + REQUIRE_THROWS_WITH_AS( + program.parse_args({"test", "red", "green2"}), + "Invalid argument \"green2\" - allowed options: {red, green, blue}", + std::runtime_error); +} \ No newline at end of file diff --git a/test/test_default_args.cpp b/test/test_default_args.cpp index 61fbabc..4b813fb 100644 --- a/test/test_default_args.cpp +++ b/test/test_default_args.cpp @@ -5,9 +5,9 @@ import argparse; #endif #include +#include #include #include -#include using doctest::test_suite; @@ -30,7 +30,7 @@ TEST_CASE("Do not exit on default arguments" * test_suite("default_args")) { argparse::ArgumentParser parser("test", "1.0", argparse::default_arguments::all, false); std::stringstream buf; - std::streambuf* saved_cout_buf = std::cout.rdbuf(buf.rdbuf()); + std::streambuf *saved_cout_buf = std::cout.rdbuf(buf.rdbuf()); parser.parse_args({"test", "--help"}); std::cout.rdbuf(saved_cout_buf); REQUIRE(parser.is_used("--help")); diff --git a/test/test_equals_form.cpp b/test/test_equals_form.cpp index 6d95c3e..a4b89a4 100644 --- a/test/test_equals_form.cpp +++ b/test/test_equals_form.cpp @@ -6,8 +6,8 @@ import argparse; #include #include -#include #include +#include using doctest::test_suite; diff --git a/test/test_get.cpp b/test/test_get.cpp index d2144a1..13db01c 100644 --- a/test/test_get.cpp +++ b/test/test_get.cpp @@ -42,7 +42,7 @@ TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) { TEST_CASE("Mismatched type for argument" * test_suite("ArgumentParser::get")) { argparse::ArgumentParser program("test"); - program.add_argument("-s", "--stuff"); // as default type, a std::string + program.add_argument("-s", "--stuff"); // as default type, a std::string REQUIRE_NOTHROW(program.parse_args({"test", "-s", "321"})); REQUIRE_THROWS_AS(program.get("--stuff"), std::bad_any_cast); } diff --git a/test/test_help.cpp b/test/test_help.cpp index ec5b6bd..db4f49f 100644 --- a/test/test_help.cpp +++ b/test/test_help.cpp @@ -5,8 +5,8 @@ import argparse; #endif #include -#include #include +#include using doctest::test_suite; @@ -82,22 +82,19 @@ TEST_CASE("Users can replace default -h/--help" * test_suite("help")) { TEST_CASE("Multiline help message alignment") { // '#' is used at the beginning of each help message line to simplify testing. - // It is important to ensure that this character doesn't appear elsewhere in the test case. - // Default arguments (e.g., -h/--help, -v/--version) are not included in this test. + // It is important to ensure that this character doesn't appear elsewhere in + // the test case. Default arguments (e.g., -h/--help, -v/--version) are not + // included in this test. argparse::ArgumentParser program("program"); - program.add_argument("INPUT1") - .help( - "#This is the first line of help message.\n" - "#And this is the second line of help message." - ); - program.add_argument("program_input2") - .help("#There is only one line."); + program.add_argument("INPUT1").help( + "#This is the first line of help message.\n" + "#And this is the second line of help message."); + program.add_argument("program_input2").help("#There is only one line."); program.add_argument("-p", "--prog_input3") .help( -R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit. + R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit. #Sed ut perspiciatis unde omnis iste natus error sit voluptatem -#accusantium doloremque laudantium, totam rem aperiam...)" - ); +#accusantium doloremque laudantium, totam rem aperiam...)"); program.add_argument("--verbose").default_value(false).implicit_value(true); std::ostringstream stream; @@ -107,7 +104,8 @@ R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit. auto help_message_start = std::string::npos; std::string line; while (std::getline(iss, line)) { - // Find the position of '#', which indicates the start of the help message line + // Find the position of '#', which indicates the start of the help message + // line auto pos = line.find('#'); if (pos == std::string::npos) { diff --git a/test/test_parse_known_args.cpp b/test/test_parse_known_args.cpp index d5a939c..909621f 100644 --- a/test/test_parse_known_args.cpp +++ b/test/test_parse_known_args.cpp @@ -5,8 +5,8 @@ import argparse; #endif #include -#include #include +#include using doctest::test_suite; diff --git a/test/test_repr.cpp b/test/test_repr.cpp index f7330bc..d924a72 100644 --- a/test/test_repr.cpp +++ b/test/test_repr.cpp @@ -6,8 +6,8 @@ import argparse.details; #endif #include -#include #include +#include #include using doctest::test_suite; diff --git a/test/test_subparsers.cpp b/test/test_subparsers.cpp index 9e7c29f..7f261b5 100644 --- a/test/test_subparsers.cpp +++ b/test/test_subparsers.cpp @@ -6,8 +6,8 @@ import argparse; #include #include -#include #include +#include using doctest::test_suite; @@ -213,8 +213,8 @@ TEST_CASE("Check is_subcommand_used after parse" * test_suite("subparsers")) { argparse::ArgumentParser command_2("clean"); command_2.add_argument("--fullclean") - .default_value(false) - .implicit_value(true); + .default_value(false) + .implicit_value(true); argparse::ArgumentParser program("test"); program.add_subparser(command_1); From 0b8d0e2426504af5829abd24474051c5c959d454 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Fri, 27 Oct 2023 10:28:54 -0500 Subject: [PATCH 2/4] Added support for integer type in choices --- README.md | 37 +++++++++++++++++++------ include/argparse/argparse.hpp | 51 ++++++++++++++++++++++++----------- test/test_choices.cpp | 21 +++++++++++++++ 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6bf2046..9b83463 100644 --- a/README.md +++ b/README.md @@ -1060,13 +1060,7 @@ argparse::ArgumentParser program("test"); program.add_argument("input") .default_value(std::string{"baz"}) - .action([](const std::string& value) { - static const std::vector choices = { "foo", "bar", "baz" }; - if (std::find(choices.begin(), choices.end(), value) != choices.end()) { - return value; - } - return std::string{ "baz" }; - }); + .choices("foo", "bar", "baz"); try { program.parse_args(argc, argv); @@ -1083,7 +1077,34 @@ std::cout << input << std::endl; ```console foo@bar:/home/dev/$ ./main fex -baz +Invalid argument "fex" - allowed options: {foo, bar, baz} +``` + +Using choices also works in integer types, e.g., + +```cpp +argparse::ArgumentParser program("test"); + +program.add_argument("input") + .default_value(0) + .choices(0, 1, 2, 3, 4, 5); + +try { + program.parse_args(argc, argv); +} +catch (const std::exception& err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); +} + +auto input = program.get("input"); +std::cout << input << std::endl; +``` + +```console +foo@bar:/home/dev/$ ./main 6 +Invalid argument "6" - allowed options: {0, 1, 2, 3, 4, 5} ``` ## Using `option=value` syntax diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index f54d4c3..0502894 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -350,6 +350,17 @@ std::string join(StrIt first, StrIt last, const std::string &separator) { return value.str(); } +template +struct can_invoke_to_string { + template + static auto test(int) -> decltype(std::to_string(std::declval()), std::true_type{}); + + template + static auto test(...) -> std::false_type; + + static constexpr bool value = decltype(test(0))::value; +}; + } // namespace details enum class nargs_pattern { optional, any, at_least_one }; @@ -408,6 +419,14 @@ public: template Argument &default_value(T &&value) { m_default_value_repr = details::repr(value); + + if constexpr (std::is_convertible_v) { + m_default_value_str = std::string{std::string_view{value}}; + } + else if constexpr (details::can_invoke_to_string::value) { + m_default_value_str = std::to_string(value); + } + m_default_value = std::forward(value); return *this; } @@ -525,12 +544,19 @@ public: return nargs(nargs_pattern::any); } - void add_choice(const std::string &choice) { + template + void add_choice(T&& choice) { + static_assert(std::is_convertible_v || details::can_invoke_to_string::value, "Choice is not convertible to string_type"); if (!m_choices.has_value()) { - /// create it m_choices = std::vector{}; } - m_choices.value().push_back(choice); + + if constexpr (std::is_convertible_v) { + m_choices.value().push_back(std::string{std::string_view{std::forward(choice)}}); + } + else if constexpr (details::can_invoke_to_string::value) { + m_choices.value().push_back(std::to_string(std::forward(choice))); + } } Argument &choices() { @@ -540,18 +566,10 @@ public: return *this; } - template - Argument &choices(const std::string &first, T &...rest) { - add_choice(first); - choices(rest...); - - return *this; - } - - template Argument &choices(const char *first, T &...rest) { - add_choice(first); - choices(rest...); - + template + Argument &choices(T&& first, U&&...rest) { + add_choice(std::forward(first)); + choices(std::forward(rest)...); return *this; } @@ -560,7 +578,7 @@ public: const auto &choices = m_choices.value(); if (m_default_value.has_value()) { - if (std::find(choices.begin(), choices.end(), m_default_value_repr) == + if (std::find(choices.begin(), choices.end(), m_default_value_str) == choices.end()) { // provided arg not in list of allowed choices // report error @@ -1152,6 +1170,7 @@ private: std::string m_metavar; std::any m_default_value; std::string m_default_value_repr; + std::optional m_default_value_str; // used for checking default_value against choices std::any m_implicit_value; std::optional> m_choices{std::nullopt}; using valued_action = std::function; diff --git a/test/test_choices.cpp b/test/test_choices.cpp index 9c17d23..d8f4d54 100644 --- a/test/test_choices.cpp +++ b/test/test_choices.cpp @@ -67,4 +67,25 @@ TEST_CASE("Parse multiple arguments one of which is not in the fixed number of " program.parse_args({"test", "red", "green2"}), "Invalid argument \"green2\" - allowed options: {red, green, blue}", std::runtime_error); +} + +TEST_CASE( + "Parse multiple arguments that are in the fixed number of allowed INTEGER choices" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); + + program.parse_args({"test", "1", "2"}); +} + +TEST_CASE( + "Parse multiple arguments that are not in fixed number of allowed INTEGER choices" * + test_suite("choices")) { + argparse::ArgumentParser program("test"); + program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); + + REQUIRE_THROWS_WITH_AS( + program.parse_args({"test", "6", "7"}), + "Invalid argument \"6\" - allowed options: {1, 2, 3, 4, 5}", + std::runtime_error); } \ No newline at end of file From 6d49d5ee1b454645062b920f017c288d0a9f8e97 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Fri, 27 Oct 2023 10:35:04 -0500 Subject: [PATCH 3/4] Limiting choices support to string type or integer type --- README.md | 2 +- include/argparse/argparse.hpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b83463..0d9afa0 100644 --- a/README.md +++ b/README.md @@ -1080,7 +1080,7 @@ foo@bar:/home/dev/$ ./main fex Invalid argument "fex" - allowed options: {foo, bar, baz} ``` -Using choices also works in integer types, e.g., +Using choices also works with integer types, e.g., ```cpp argparse::ArgumentParser program("test"); diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 0502894..12e9b07 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -361,6 +361,15 @@ struct can_invoke_to_string { static constexpr bool value = decltype(test(0))::value; }; +template +struct is_choice_type_supported { + using CleanType = typename std::decay::type; + static const bool value = std::is_integral::value || + std::is_same::value || + std::is_same::value || + std::is_same::value; +}; + } // namespace details enum class nargs_pattern { optional, any, at_least_one }; @@ -546,6 +555,7 @@ public: template void add_choice(T&& choice) { + static_assert(details::is_choice_type_supported::value, "Only string or integer type supported for choice"); static_assert(std::is_convertible_v || details::can_invoke_to_string::value, "Choice is not convertible to string_type"); if (!m_choices.has_value()) { m_choices = std::vector{}; From fd726fd3413875d179057cb67bb53fb0c551cc9e Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Fri, 27 Oct 2023 10:53:52 -0500 Subject: [PATCH 4/4] Fixed clang tidy warning about struct name --- include/argparse/argparse.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 12e9b07..9c71697 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -362,7 +362,7 @@ struct can_invoke_to_string { }; template -struct is_choice_type_supported { +struct IsChoiceTypeSupported { using CleanType = typename std::decay::type; static const bool value = std::is_integral::value || std::is_same::value || @@ -555,7 +555,7 @@ public: template void add_choice(T&& choice) { - static_assert(details::is_choice_type_supported::value, "Only string or integer type supported for choice"); + static_assert(details::IsChoiceTypeSupported::value, "Only string or integer type supported for choice"); static_assert(std::is_convertible_v || details::can_invoke_to_string::value, "Choice is not convertible to string_type"); if (!m_choices.has_value()) { m_choices = std::vector{};