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.

Categories: Code

Yu

Ideals are like the stars: we never reach them, but like the mariners of the sea, we chart our course by them.

2 Comments

xqiushi · February 15, 2018 at 03:39

Google Chrome 64.0.3282.167 Google Chrome 64.0.3282.167 Mac OS X  10.13.3 Mac OS X 10.13.3

新年快乐

    Yu · April 4, 2019 at 12:18

    Google Chrome 73.0.3683.86 Google Chrome 73.0.3683.86 Mac OS X  10.14.3 Mac OS X 10.14.3

    好久不见。靴靴

Leave a Reply to Yu Cancel reply

Your email address will not be published. Required fields are marked *