Get number of arguments passed to variadic function/method

If I have a class like this:

1
2
3
4
5
6
7
template<class T>
class Example {
private:
 std::map<char,T> va;
public:
 Example(char var...);
}


And I wanted allow declarations like that: Example<int> e('x') or Example<int> e('x', 'y'), how I should implement the constructor for the class?

I have this idea, but it needs a way to get the number of arguments passed to the method:

1
2
3
4
5
6
7
8
9
10
11
Example(char var...) {
    va_list args;
    va_start(args, var);
    va.insert({var, T()});
    int num = ?
    for (i = 0; i < num; i++) {
        char c = va_arg(valist, char);
        va.insert({c,T()});
    }
    va_end(args);
}


Is this possible? If so, how I get this number? Also, can I get a compilation error if 2 identical values are passed to the constructor (like Example<int> e('x', 'x') or Example<int> e('x', 'y', 'y')?
In C/C++, you can not determine the number of arguments that were passed to a "variadic" function.

You either need to pass the number of arguments explicitly, in a separate count parameter, or you need to append a special "terminator" element that marks the end of the arguments list, usually NULL.

This can be simplified with a macro:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define MY_FUNCTION(X, ...) my_function((X), __VA_ARGS__, NULL)
void my_function(const char *param, ...)
{
  va_list list;
  char *val;
  va_start(list, param);
  puts(param);
  while (val = va_arg(list, char*))
  {
    puts(val);
  }
  va_end(list);
}

int main()
{
  MY_FUNCTION("foo", "bar", "baz");
}
Last edited on
this solution can't be done with methods/constructor of class then?
You can use a "variadic template" instead.

https://en.cppreference.com/w/cpp/language/parameter_pack

1
2
3
4
5
6
7
8
9
10
11
template<class T>
class Example {
private:
	std::map<char,T> va;
public:
	template<class... Args>
	Example(Args... args)
	:	va{{args, T()}...}
	{
	}
};


But I don't think there is a way to turn 2 identical arguments into a compilation error. At least not with this approach.
Last edited on
Ok, this solution works for me, but my implementation is only working when I declare the object with 1 argument. My implementation:

1
2
3
4
5
6
7
8
9
10
11
template<class T>
class Example {
private:
 std::map<char,T> va;
public:
  template<typename... Params>
  Example(Params... params) {
    for(auto c : {params...})
      va.insert({c, T()});
  }
}



When I have Example<int> e('x') or Example<float> e('x') everything works with no issues. But when I have Example<int> e('x', 'y') or Example<float> e('x', 'y') I got a compilation error with the line va.insert({c, T()});.
I got a compilation error

Perhaps you forgot to include initalizer_list, which might be needed for
for(auto c : {params...})
which relies on it implicitly. It's a little unlikely but worth a try.

Otherwise, please post enough code to reproduce the problem. This sample program works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <map>
#include <initializer_list>

template<class T>
class Example {
private:
 std::map<char,T> va;
public:
  template<typename... Params>
  Example(Params... params) {
    for(auto c : {params...})
      va.insert({c, T()});
  }
};

int main()
{
  Example<float> e('x', 'y');
}
Last edited on
I already had all the #include needed for the code compile. The complete code I am trying to compile and run is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <map>
#include <cstdarg>
#include <vector>
#include <functional>
#include <initializer_list>
#include <iostream>

template<typename T, typename ... Args>
class Function {
private:
  std::map<char,T> va;
  std::vector<std::function<T(T, Args...)>> terms;
public:
  template<typename... Params>
  Function(Params... params) {
    for(auto c : {params...})
      va.insert({c, T()});
  }

  T operator()(T t, Args ... args) {
    T result = 0;
    for(long unsigned int i=0; i<terms.size(); i++) result += terms[i](t, args...);
    return result;
  }

  Function<T>& operator=(std::initializer_list<std::function<T(T, Args...)>> array) {
    for(long unsigned int i=0; i<array.size(); i++) terms.push_back(std::data(array)[i]);
    return *this;
  }
};

int p5(int x, int y) {
  return x*y;
}

int p6(int x, int y) {
  return x+y;
}

float p7(float x, float y) {
  return x*y;
}

float p8(float x, float y) {
  return x+y;
}

int main(int argc, char ** argv) {
  Function<int> f1('x', 'y');
  f1 = {p5, p6};
  std::cout << f1(3, 2) << std::endl;

  Function<float> f2('x', 'y');
  f2 = {p7, p8};
  std::cout << f2(2.5f, 1.5f) << std::endl;

  return 0;
}


and the error in the line is:

1
2
error: no matching function for call to ‘std::map<char, int, std::less<char>, std::allocator<std::pair<const char, int> > >::insert(<brace-enclosed initializer list>)’
   19 |       va.insert({c, T()});
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <map>
#include <cstdarg>
#include <vector>
#include <functional>
#include <initializer_list>
#include <iostream> // <-- 

template<typename T, typename ... Args>
class Function {
private:
  std::map<char,T> va;
  std::vector<std::function<T(T, Args...)>> terms;
public:
  template<typename... Params>
  Function(Params... params) {
    for(auto c : {params...})
      va.insert({c, T()});
  }

  T operator()(T t, Args ... args) {
    T result = 0;
    for(long unsigned int i=0; i<terms.size(); i++) result += terms[i](t, args...);
    return result;
  }


  Function& operator=(std::initializer_list<std::function<T(T, Args...)>> array) { // <-- 
    for(long unsigned int i=0; i<array.size(); i++) terms.push_back(std::data(array)[i]);
    return *this;
  }
};

int p5(int x, int y) {
  return x*y;
}

int p6(int x, int y) {
  return x+y;
}

float p7(float x, float y) {
  return x*y;
}

float p8(float x, float y) {
  return x+y;
}

int main(int, char **) {
  Function<int, int> f1('x', 'y'); // <-- 
  f1 = {p5, p6};
  std::cout << f1(3, 2) << std::endl;

  Function<float, float> f2('x', 'y'); // <-- 
  f2 = {p7, p8};
  std::cout << f2(2.5f, 1.5f) << std::endl;

  return 0;
}
Last edited on
It is not that. this line was present in my code already, I just forgot to copy here in the forum. As I said, the error is pointed in the line va.insert({c, T()}); of the constructor, but only when I have more of 1 argument.
If I change the code to:

1
2
3
4
5
  template<typename... Params>
  Function(Params... params) {
    for(char c : {params...})
      va.insert({c, T()});
  }


now the error I got (in line for(char c : {params...}) ) is:

 
error: invalid conversion from ‘int (*)(int, int)’ to ‘char’ [-fpermissive


Last edited on
It is not that.

There were three additional corrections aside from the missing header in the code above.

The program above is accepted by all three mainstream compilers. I tried them before posting it.
Last edited on
@klebermo

Your error message comes from this line:
 
f1 = {p5, p6};

It tries to call the constructor as if you had written:
 
f1 = Function<int>{p5, p6};

That means c is a function pointer and why va.insert({c, T()}); does not work.

The reason it doesn't call your operator= is because the parameter type doesn't match.

The operator= of a Function<int> expects a std::initializer_list<std::function<int(int)>> as argument. I.e. an initializer list of functions that return an int and takes exactly one int as argument. But the functions that you try to pass take two ints as argument.

mbozzi solved this issue by instead using Function<int, int> which has an operator= that expects functions that take two ints as arguments.

This is the same mistake you did in an earlier thread:
https://cplusplus.com/forum/beginner/284037/
Last edited on
Topic archived. No new replies allowed.