argparse icon indicating copy to clipboard operation
argparse copied to clipboard

What if actions want to return std::vector?

Open zhihaoy opened this issue 4 years ago • 5 comments

If an option wants to parse a list of values --foo 1,4,2,6, then it cannot return std::vector<int> because this type is considered a container. We probably will never want to parse a list like that (because there are so many good parsing libraries for structural data), so we cannot return the existing containers functionality to "make get<std::vector<T>>() work" for this demand.

We should consider changing the multivalue argument support. Some observations:

  • Blessing types takes away possible return types
  • std::vector<std::string> is verbose to spell
  • std::array is not supported (and is tricky to support, because it's not a container that can grow)
  • std::list is probably never used for lots of reasons

Inventing a new set of API is an option, but the present API (#66) will double the number of names. I think the situation can be improved by blessing a type that is both impossible to be stored in std::any, and is terse to write -- array type.

Here is how the new get may look like:

  • get<T[]>: returns std::vector<T>
  • get<T[N]>: returns std::array<T, N> if N == nargs, throws otherwise
  • get<T>: returns T

The old spelling get<std::vector<std::string>> becomes get<std::string[]> after the change. To not to break everything immediately, we can make the old spelling [[deprecated]].

FAQ

  1. Why T[] and T[N] return entirely different types? I hope users can interpret [] as "returning a type with operator[]."
  2. Why not other kinds of containers? If users want they can append to anything with void actions. We are providing a good default here.
  3. Why not std::span (of static extent or dynamic extent)? Technical issue, we only have storage for std::vector<std::any> rather than std::vector<T>.

zhihaoy avatar Dec 01 '19 08:12 zhihaoy

Actions were designed to be similar to the visitor pattern in ANTLR - visit each argument/fragment, make changes given some context and save it. The parameter to .action() is a const string& representing each fragment, e.g.,

$./program --foo 1 4 3 6

Here, action receives 1, then 4, then 3, and finally 6. Inside the visitor, I can convert each fragment to whatever type I want, e.g., MyStringWrapper(arg) and return it.

How does this change in your proposal? What is the argument to .action(). Is it now a vector of strings if nargs > 1?

p-ranav avatar Dec 02 '19 14:12 p-ranav

I know this part, but what if users want to do this:

program.action([](std::string const& s) {
   return std::vector<int>(std::stoi(s));  // let's say it's a vector of N random numbers
});

Then the user cannot use program.get<std::vector<int>>("..."); to get the value, because that ends up in the container case, and the value type is deemed int. The "real" value type is std::vector<int>, so bad_any_cast will be thrown.

zhihaoy avatar Dec 02 '19 16:12 zhihaoy

Understood. Just need some clarifications:

What is the argument s in your example? Let's say the use-case is --foo 1 2 3 4

Then is s == 1? What is the action returning? The vector {1} then the vector {2} and so on?

.. Or is s == "1 2 3 4" which is the whole argument and the action returns {1, 2, 3, 4}? - This is clearly not the case per your example but it might be the intention.

Just need to clarify the semantics here.

p-ranav avatar Dec 02 '19 16:12 p-ranav

s is "1" in --foo 1. It's "1" then "2" in --foo 1 2 but I don't meant to bring in nargs > 1. It's an nargs == 1 argument, but wants to return a std::vector.

zhihaoy avatar Dec 02 '19 17:12 zhihaoy

Ah I see now. Okay, makes sense.

p-ranav avatar Dec 02 '19 17:12 p-ranav

Parsing a list of values is now supported with .nargs(...). See here for more details. Closing this now.

p-ranav avatar Sep 21 '22 13:09 p-ranav