1

I'm setting up a console command that takes a variable number of arguments, each of which can be basic types (int, float, bool, string) and then pass them to a function that has 8 overloads for supporting a different number of arguments of varying types. How would I parse the command line string into values based on their type and then pass them to the function?

I can retrieve each argument via the function const char* GetArg(int index). Converting the char* to the proper type isn't an issue so don't worry about that part. Storing the values and somehow passing to the template function is the part I'm stuck on.

For example, if the command was executed with the following string: "command 66 true 5 "string value" 0.56"

It would then be broken up into the following args and stored somehow:

int arg1 = GetArg(1); // 66
bool arg2 = GetArg(2); // true
int arg3 = GetArg(3); // 5
char* arg4 = GetArg(4); // "string value"
float arg5 = GetArg(5); // 0.56

And then based on the number of args, call the correct template function:

// The function definition looks something like this:
void SomeFunc();
template<typename T1>
void SomeFunc(const T1& arg1);
template<typename T1, typename T2>
void SomeFunc(const T1& arg1, const T2& arg2);
// etc...

// And then somehow it would be called. This is just an example. I don't
// know how to call it in a way that would work with variable number and
// type of args.
switch (argCount)
{
case 0:
    SomeFunc();
    break;
case 1:
    SomeFunc(arg1);
    break;
case 2:
    SomeFunc(arg1, arg2);
    break;
case 3:
    SomeFunc(arg1, arg2, arg3);
    break;
case 4:
    SomeFunc(arg1, arg2, arg3, arg4);
    break;
case 5:
    SomeFunc(arg1, arg2, arg3, arg4, arg5);
    break;
}

How would you make this possible? Storing the args in some way that can be passed to the template function so that it knows the type of each argument doesn't seem possible, but I feel like I'm just not thinking of something.

I can't change this interface either. This is a third party function that I just have to deal with. So no matter how it is implemented, eventually it has to go through SomeFunc().

IMPORTANT: I'm doing this in Visual Studio 2012 so I'm rather limited on newer C++ features. It can do a little bit of C++11 but that's it. Trying to upgrade the project to a newer version but for now that's what I have to deal with.

Justin G
  • 776
  • 1
  • 10
  • 25
  • It sounds like you are trying to mix compile-time and run-time logic. Templates are compile time, and you need to know the number of parameters and their type at compile time. Is it really something else you are trying to achieve? – Tommy Andersen Oct 18 '17 at 20:02
  • Well if you can build a tuple then you can use [this](https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer) to turn the tuple into a function call. – NathanOliver Oct 18 '17 at 20:05
  • @TommyAndersen That sounds accurate. I don't really have any way around that. I'm just trying to make a helper console command for easily triggering events, which can have variable number of args and types for each one. If this gets out of hand I'll probably just punt on the idea, which it's starting to seem like might be the case. – Justin G Oct 18 '17 at 21:35
  • @NathanOliver That's super neat! But wouldn't I have to build a tuple for every single permutation of the combination of number of args and their types? That's a crazy amount of tuples. That's a big part of this problem that I have no idea how to deal with. Even if I can figure out the type of each argument, I have no idea how to store them in some common container so it doesn't require an insane number of permutations. – Justin G Oct 18 '17 at 21:40
  • OK, I'm probably going to punt on this idea since it doesn't seem possible. I'll just stick with support for passing no arguments. It's just meant for debugging and testing anyways so not super important :) Thanks for the info everyone! – Justin G Oct 19 '17 at 17:29

1 Answers1

1
using basic_type = std::variant<int, float, bool, std::string>;

using flat_arguments = std::vector<basic_type>;

template<std::size_t...Ns>
using packed_arguments = std::variant< std::array<basic_type, Ns>... >;

template<class T, std::size_t...Ns>
std::array<T, sizeof...(Ns)> pack_one( std::vector<T> n, std::index_sequence<Ns...> ) {
  return {{ std::move(n[Ns])... }};
}

template<class T, std::size_t...Ns>
std::optional<std::variant< std::array<T, Ns>... >>
pack_all(std::vector<T> n, std::index_sequence<Ns...> ) {
  std::optional<std::variant< std::array<T, Ns>... >> retval;
  if (n.size() >= sizeof...(Ns)) { return retval; }
  (
    (
      (n.size()==Ns)?
        void(retval.emplace( pack_one( std::move(n), std::make_index_sequence<Ns>{} ):
        void()
    ),...
  );
  return retval;
}

flat_arguments get_arguments( int argc, char const* const*argv); // write this

auto invoke_somefunc = [](auto&&...args){
  return SomeFunc( decltype(args)(args)... );
};

int main(int argc, char const*const* argv) {
  auto args = get_arguments(argc, argv);
  auto args_packed = pack_all(std::move(args), std::make_index_sequence<9>{} );
  if (!args_packed) return -1;
  std::visit( [](auto&& args){
    std::apply( [](auto&&...args){
      std::visit( invoke_somefunc, args... );
    }, args );
  }, args_packed );
}

that should do it. Probably contains typos. .

boost has equivalent types (variant and optional) that could replace the std use above, with some tweaks.

The fold pack expansion can be replaced with the expand-in-array hack in or greater.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Sorry, I forgot to mention I'm rather limited on newer language features. I'm doing this in VS2012 so I don't think most of this is available. I updated the original post with that information. – Justin G Oct 18 '17 at 21:37
  • @Shenjoku Oh, then no, you cannot. I mean, there is the Turing Tar Pit, where you can do anything, but you'd basically be expanding the above work manually into a whole mess of spaghetti code. Far easier to upgrade your C++ compiler than write it in the sublanguage of C++ that VS 2012 supports. Maybe if you can find an ancient version of boost that supports variant yet works in VS 2012 and manually write the function objects to replace the lambdas and all that pain. – Yakk - Adam Nevraumont Oct 18 '17 at 21:39