Fix various issues in Argument constructor

Before this change:

1. When the input is built-in string literal or cv-`char*`,
   `is_optional` constructs temporary `std::string` while
   `mNames` initializer is also constructing `std::string`
   due to the use of `std::initializer_list`.
2. When the input is `std::string_view`, doesn't compile.
3. When the input is `std::string`, `mNames` initializer
   moves `args`.  If argument name is longer than
   `std::string`'s SSO buffer, bad thing will happen because
   `is_optional` will be accessing `args` in moved-from
   states.

Because of the use of `strtol` which expects nul-terminated
input, `is_*` series functions must take `std::string`.  This
restriction may be removed after AppleClang adds `<charconv>`.
But for now, it complicates the patch.  My solution is to
create an array prvalue still, but use a array reference
rather than `std::initializer_list` to refer to it, so that
the code in delegated constructor can keep using fold
expressions after the necessary `std::string` objects being
created.
This commit is contained in:
Zhihao Yuan 2019-11-17 00:30:55 -06:00
parent 7dd6655a9e
commit 8201a18568
No known key found for this signature in database
GPG Key ID: A2E474BDAA37E11C
2 changed files with 36 additions and 8 deletions

View File

@ -99,19 +99,28 @@ class Argument {
friend auto operator<<(std::ostream &, ArgumentParser const &) friend auto operator<<(std::ostream &, ArgumentParser const &)
-> std::ostream &; -> std::ostream &;
public: template <size_t N, size_t... I>
Argument() = default; explicit Argument(std::string(&&a)[N], std::index_sequence<I...>)
: mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false),
template <typename... Args> mIsUsed(false) {
explicit Argument(Args... args) ((void)mNames.push_back(std::move(a[I])), ...);
: mNames({std::move(args)...}), mIsOptional((is_optional(args) || ...)),
mIsRequired(false), mIsUsed(false) {
std::sort( std::sort(
mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) { mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) {
return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
}); });
} }
public:
Argument() = default;
template <typename... Args,
std::enable_if_t<
std::conjunction_v<std::is_constructible<std::string, Args>...>,
int> = 0>
explicit Argument(Args &&... args)
: Argument({std::string(std::forward<Args>(args))...},
std::make_index_sequence<sizeof...(Args)>{}) {}
Argument &help(std::string aHelp) { Argument &help(std::string aHelp) {
mHelp = std::move(aHelp); mHelp = std::move(aHelp);
return *this; return *this;

View File

@ -44,3 +44,22 @@ TEST_CASE("Parse multiple toggle arguments with implicit values", "[optional_arg
REQUIRE(program.get<bool>("-u") == false); REQUIRE(program.get<bool>("-u") == false);
REQUIRE(program.get<bool>("-x") == true); REQUIRE(program.get<bool>("-x") == true);
} }
TEST_CASE("Parse arguments of different types", "[optional_arguments]") {
using namespace std::literals;
argparse::ArgumentParser program("test");
program.add_argument("--this-argument-is-longer-than-any-sso-buffer-that-"
"makes-sense-unless-your-cache-line-is-this-long"s);
REQUIRE_NOTHROW(program.parse_args({"test"}));
program.add_argument("-string"s, "-string-view"sv, "-builtin")
.default_value(false)
.implicit_value(true);
program.parse_args({"test", "-string-view"});
REQUIRE(program["-string"sv] == true);
REQUIRE(program["-string-view"] == true);
REQUIRE(program["-builtin"s] == true);
}