Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a function accept arbitrary number of arguments not using f(...)?

A piece of code is worth a thousand words:

int main()
{
    // All of the following calls return true:
    AreEqual(1, 1);
    AreEqual(1, 1, 1);
    AreEqual(1, 1, 1, 1);
    AreEqual(1, 1, 1, 1, 1);

    // All of the following calls return false:
    AreEqual(1, 2);
    AreEqual(1, 2, 1);
    AreEqual(1, 7, 3, 1);
    AreEqual(1, 4, 1, 1, 1);    
}

How to implement the function AreEqual() that accepts arbitrary number of arguments?

The trivial but tedious soultion is through overloading:

bool AreEqual(int v1, int v2);
bool AreEqual(int v1, int v2, int v3);
bool AreEqual(int v1, int v2, int v3, int v4);
......

Another trivial but not workable solution is:

bool AreEqual(...);

This solution is not workable, because the caller must add another argument (argument count or ending marker) to specify the number of the arguments.

Yet another way is through variadic template arguments

template<class... Args>
bool AreEqual(Args... args)
{
    // What should be placed here ???
}
like image 232
xmllmx Avatar asked Jan 29 '13 20:01

xmllmx


2 Answers

Here is how one can implement it with templates:

#include <iostream>
#include <iomanip>

template<class T0>
bool AreEqual(T0 t0) { return true; }

template<class T0, class T1, class... Args>
bool AreEqual(T0 t0, T1 t1, Args ... args) {
  return t0 == t1 && AreEqual(t1, args...);
}


int main () {
  std::cout << std::boolalpha;

    // All of the following calls return true:
  std::cout<< AreEqual(1, 1) << "\n";
  std::cout<< AreEqual(1, 1, 1) << "\n";
  std::cout<< AreEqual(1, 1, 1, 1) << "\n";
  std::cout<< AreEqual(1, 1, 1, 1, 1) << "\n\n";

    // All of the following calls return false:
  std::cout<< AreEqual(1, 2) << "\n";
  std::cout<< AreEqual(1, 2, 1) << "\n";
  std::cout<< AreEqual(1, 7, 3, 1) << "\n";
  std::cout<< AreEqual(1, 4, 1, 1, 1)  << "\n";
}

You should consider whether these variations are appropriate for your use:

  • Use references instead of pass-by-value
  • Make the non-variadic template take two parameters instead of one.


Alternatively, here is a non-recursive version. Sadly, it does not short-curcuit. To see a non-recursive short-circuiting version see the other answer.
template<typename T, typename... Args>
bool AreEqual(T first, Args... args)
{
  return std::min({first==args...});
}


Finally, this version is attractive in its lack of subtlety:
template<typename T, typename... Args>
bool AreEqual(T first, Args... args)
{
  for(auto i : {args...})
    if(first != i)
      return false;
  return true;
}
like image 113
Robᵩ Avatar answered Oct 13 '22 20:10

Robᵩ


Since you seem to be ruling out the sensible way to do it for some reason you could also try using std::initializer_list:

template<typename T>
bool AreEqual(std::initializer_list<T> list) {
    ...
}

Then you'd call it like:

AreEqual({1,1,1,1,1});
like image 20
bames53 Avatar answered Oct 13 '22 19:10

bames53