xmllmx
xmllmx

Reputation: 42379

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 ???
}

Upvotes: 6

Views: 623

Answers (5)

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20759

The varadic template requires recursion on specialization:

template<class A> //just two: this is trivial
bool are_equal(const A& a, const A& b)
{ return a==b; } 

template<class A, class... Others>
bool are_equal(const A& a, const A& b, const Others&... others)
{ return are_equal(a,b) && are_equal(b,others...); }

In essence every time are_equal is nested, others... will short by one until estinguish, and the function will bind the two args.

Note: By using A as a type for both a and b, and because Others's first has always to match an A, this in-fact- makes are_equal(...) to accept all arguments only of a same type (ot at least, convertible into the type of the first argument).

Although I fond this constrain generally useful, the limitation can be relaxed by using A and B as types for a and b This makes the function to work with every set of types for which an operator== exist for each of its pairs.

template<class A, class B> //just two: this is trivial
bool are_equal(const A& a, const B& b)
{ return a==b; } 

template<class A, class B, class... Others>
bool are_equal(const A& a, const B& b, const Others&... others)
{ return are_equal(a,b) && are_equal(b,others...); }

Upvotes: 1

Robᵩ
Robᵩ

Reputation: 168866

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;
}

Upvotes: 11

ipc
ipc

Reputation: 8143

Here is a non-recursive version:

template <typename T> using identity = T;

template<typename T, typename... Args>
bool AreEqual(T first, Args... args)
{
  bool tmp = true;
  identity<bool[]>{tmp?tmp=first==args:true ...};
  return tmp;
}

With optimizations on, there is no overhead compared to the recursive functions except that the behavior can be different since all arguments are compared to the first one.

Upvotes: 3

bames53
bames53

Reputation: 88225

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});

Upvotes: 4

David Schwartz
David Schwartz

Reputation: 182875

Use

 bool AreEqual(int v1, ...);

You will need, however, to detect the end of the list of integers somehow. If there's a particular integer value that is not legal to pass to the function, then use that. For example, if all integers are positive, you can use -1 to mark the end. Otherwise, you can make the first parameter a count.

Here's the count version:

bool AreEqual(int count, int v1, ...)
{
     va_list vl;
     va_start(vl, count);
     for(int i = 1; i < count; ++i)
        if (va_arg(v1, int) != v1)
        {
            va_end(vl);
            return false;
        }
     va_end(vl);
     return true;
}

And here's the end marker version:

bool AreEqual(int v1, ...)
{
     va_list vl;
     va_start(vl, count);

     do
     {
         int param = va_arg(vl, int);
         if (param == -1)
         {
             va_end(vl);
             return true;
        }
    } while (param == v1);
    va_end(vl);
    return false;
}

Upvotes: 1

Related Questions