Sometimes, we are supposed to accept uncertain number of arguments in C++.
In this post, I am going to introduce 3 ways to implement it, so that you can pick up one of them which is most fit for you in current situation.
1. va_list
va_list is coming from C lang. Had you ever considered how to implement the function "printf"? Of course, they are using va_list.
Here is a tiny example
/* output: <stdin>:23 args_copy[0]=3 <stdin>:23 args_copy[1]=5 sum of: 1, 2, 3 = 9 */ #include <stdarg.h> #include <stdio.h> int sum(int count, ...) { int sum = 0; va_list args; va_start(args, count); va_list args_copy; for (int j = 0; j < count; j++) { sum += va_arg(args, int); if (j == 0) { // args_copy will copy args to args_copy from current va_copy(args_copy, args); } } va_end(args); for (int j = 0; j < (count - 1); j++) { printf("%s:%d args_copy[%d]=%d\n", __FILE__, __LINE__, j, va_arg(args_copy, int)); } va_end(args_copy); return sum; } int main() { printf("sum of: 1, 2, 3 = %d\n", sum(3, 1, 3, 5)); }
Please refer to this link and try yourself. You can also refer to this link for its documentation.
You will use va_start, va_copy, va_arg, va_end to initialize, copy, scan and finalize the args easily.
The positive is you can use it either in C or C++, but the negative is you can't get the count and type of args.
2. initializer_list
initializer_list is a lightweight solution to pass a set of args to some function.
Here is a simple example for initializer_list
/* output got: 1 got: 2 got: 3 got: 4 total size: 4 sum of them is 10 */ #include <initializer_list> #include <iostream> #include <string> using std::cin; using std::cout; using std::endl; using std::initializer_list; using std::string; int sum(initializer_list<int> il) { int sum = 0; for (auto i : il) { cout << "got: " << i << endl; } cout << "total size: " << il.size() << endl; for (auto it = il.begin(); it != il.end(); it++) { sum += *it; } return sum; } int main() { cout << "sum of them is " << sum({1, 2, 3, 4}) << endl; }
Please refer to this link and try yourself.
The positive is you can use it easily in construction function, and gather results like a vector. The negative is it is pretty hard to pass a set of args in different type.
3. template
Template is a pretty useful feature in C++.
In this way, we can create a few functions, and compiler will automatically create related functions in compile time.
Here is a small example using template parameters:
/* output: accept: add type int, value1 accept: add type double, value2 accept: add type string, value abc=>3 sum of them is 6 */ #include <iostream> #include <string> using std::cin; using std::cout; using std::endl; using std::string; class S { public: S() : sum_(0) {} template <class... Args> S(const Args &... args) : sum_(0) { Add(args...); } S &Add(int i) { sum_ += static_cast<double>(i); cout << "accept: add type int, value" << i << endl; return *this; } S &Add(double i) { sum_ += i; cout << "accept: add type double, value" << i << endl; return *this; } S &Add(const string &s) { sum_ += s.length(); cout << "accept: add type string, value " << s << "=>" << s.length() << endl; return *this; } template <class First, class... Rest> S &Add(const First &first, const Rest &... rest) { return Add(first).Add(rest...); } double val() { return sum_; } private: double sum_; }; int main() { S s(1, 2.0, string("abc")); cout << "sum of them is " << s.val() << endl; }
Please refer to this link and try yourself.
The positive is you can handle a lot of certain situation, and will quickly failed in compile time, the negative is it really spend a lot of time in coding.
4. template + initializer_list
If we wish to implement a API, which will accept a set of similar args. we can also use template as the frontend, and use initializer_list as the backend.
For example, if we need to accept a set of string args (whatever the input is string, const string, or const char *), the implement may seems as follow:
/* output: got: a got: bc got: def the result is: abcdef */ #include <initializer_list> #include <iostream> #include <string> using std::cin; using std::cout; using std::endl; using std::string; using std::initializer_list; std::string StrCatImpl(initializer_list<const std::string> sl) { std::string base; for (auto s : sl) { cout << "got: " << s << endl; base += s; } return base; } template <typename... T> string StrCat(const T &... args) noexcept { return StrCatImpl({args...}); } int main() { std::string s = StrCat("a", "bc", string("def")); cout << "the result is: " << s << endl; }
Please refer to this link and try yourself.
2 Comments
xqiushi · February 15, 2018 at 03:39
新年快乐
Yu · April 4, 2019 at 12:18
好久不见。靴靴