简体   繁体   中英

C++ Generic command parser using variadic templates

I'm trying to write some sort of command handler, which can tokenize an istringstream, automatically convert the tokens into variables of specific types and call a callback function with the converted variables as arguments. Here is simplified version of my code:

void Callback(int x, char y, float z) {
  // do whatever
  // note: For simplicity, I use a callback with a fixed signature
  //       here. In my actual implementation, the callback can be
  //       called with any number and types of arguments - but that
  //       I have solved already.
}

template<typename T>
T GetNextArgument(std::istringstream& strm) {
  // get one token from the input stream and convert it to the required type
  T val;
  strm >> val;
  return val;
}

template<typename ...Args>
void ParseAndExecute(std::istringstream& input_stream) {
  Callback(GetNextArgument<Args>(input_stream)...);
}

int main() {
  std::istringstream strm("15 a 17.3");
  ParseAndExecute(strm);
  return 0;
}

The problem I have is that the ParseAndExecute() function after parameter pack expansion looks like this:

void ParseAndExecute(std::istringstream& strm) {
  Callback(GetNextArgument<int>(strm), 
           GetNextArgument<char>(strm),
           GetNextArgument<float>(strm));
}

Since the order of evaluation of the arguments is not defined, the tokens may be taken from the stream in incorrect order (and in my case, they always are). Instead I would need the expansion to give me something more like that:

void ParseAndExecute(std::istringstream& strm) {
  int a1 = GetNextArgument<int>(strm);
  char a2 = GetNextArgument<char>(strm);
  float a3 = GetNextArgument<float>(strm);
  Callback(a1, a2, a3);
}

But I cannot see how to achieve that with parameter pack expansion. Maybe with a recursive template...? Or do you have any other suggestion to achieve a similar functionality?

You could use an intermediate std::tuple with list initialization because left-to-right order is mandatory in this case:

std::tuple<Args...> tuple_args = {GetNextArgument<Args>(input_stream)... };
std::apply([](auto&&... args) { 
    Callback(std::forward<decltype(args)>(args)... );
 }, std::move(tuple_args));

You have to use a lambda if Callback does not have a fixed signature as you mentioned and you want to rely on deduction.

struct Caller {
    template<class...Args>
    Caller(Args&&... args) { Callback(std::forward<Args>(args)...); }
};

template<typename ...Args>
void ParseAndExecute(std::istringstream& input_stream) {
  Caller{GetNextArgument<Args>(input_stream)...};
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM