Issue with NULL in variable argument function

I am trying to make a function that randomly chooses a single argument to return from a variable number of arguments of a single datatype.

I am running Windows 7, and I am using Microsoft Visual C++ 2010 Express

Currently, the function is called like so:

 
char a = choose('a','b','c',NULL);


This should (and does) assign a random character from the given arguments to a.
In this case, a would ultimately equal 'a', 'b', or 'c'.

The final argument should always be NULL. The function will continue to check arguments for possible return values until the argument NULL is found.

The problem arises when I want to use a value equivalent to zero as an argument, such as the integer 0 or the character '\0'.
The function simply ignores the zero (or '\0'), as well as any arguments following it.

For example:
 
int b = choose(5, 9, 3, 0, 17, 16, 92, NULL);

This will assign either 5, 9, or 3 to the variable b. Everything after 0, including 0 itself, is completely ignored. This is obviously not how the function is supposed to behave.

It would seem that 0, NULL, and '\0' all have equivalent values, so if any of them are entered before the true final argument, then they are recognized as the final argument, and all arguments after them are ignored accordingly.

I've tried replacing NULL with (char)1, but that yielded the same issue, only with ones being excluded instead of zeroes.
Is there something other than NULL that I could use for my final argument that wouldn't yield such an issue? (such as a constant value that can only be obtained by explicitly entering its name)

I would prefer a solution that would still support the use of a template, but if that's impossible, then I'll take what I can get.

Thanks in advance; your help will be appreciated.

Here is the code for the function itself:
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
template <typename T>
T choose (T arg1, ...)
{
   int argsCount = 0;                                   //the number of arguments
   va_list list;                                        //declares the variable argument list
   va_start(list,arg1);                                 //starts the variable argument list
   T currentArg;                                        //the current argument within the loop
   do                                                   //a do while() loop is used instead of a while() loop just in case the garbage in currentArg is a NULL character
   {
      currentArg = va_arg(list,T);                      //assign the value of the current argument in the list to currentArg
      if (currentArg != NULL)
      {
         argsCount++;                                   //increment the number of arguments
      }
   } while(currentArg != NULL);                         //while the current argument is not NULL. This loop gets the number of arguments.
   va_end(list);                                        //resets the variable argument list to NULL so that it can be called again properly
   int choice = random.roll_range(-1, argsCount - 1);   //sets the location of the choice in the list to a random int >=-1 and <=argsCount. If -1, then return the first argument.
   if ( choice == -1 )
   {
      return arg1;                                      //returns arg1, the first argument in the function call
   }
   else                                                 //if choice is not -1, then an argument from the variable argument list must be chosen.
   {
      va_start(list,arg1);                              //starts the variable argument list again
      for( int i = 0; i < argsCount; i++ )
      {
         T currentArg = va_arg(list,T);                 //assign the value of the current argument in the list to currentArg
         if (i == choice)
         {
            return currentArg;                          //returns the chosen argument
         }
      }
      va_end(list);                                     //resets the variable argument list to NULL
      return NULL;                                      //returns NULL if the function failed to choose an argument
   }
}


Note: random.roll_range returns a random integer between two integer parameters, min and max. I know for a fact that this is not where the issue is occurring, but just in case it is necessary, here is the code for random.roll_range:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int CRandom::roll_range(int min, int max)
{
   if ( min < max )
   {
      return ( rand() % (max + 1 - min) ) + min;
   }
   else if ( min > max )                           //This ensures the function will behave properly even if the arguments were entered backwards
   {
      return ( rand() % (min + 1 - max) ) + max;
   }
   else                                            //If both arguments are equal, then it does not matter which is returned, so return max.
   {
      return max;
   }
}
Last edited on
Just make the first argument be an unsigned int equal to the number of arguments to choose between? Otherwise this will always be an issue, especially if you want it to work across a variety of data types. I'd suggest using a longer watch value like 0xDEADBEEF (thanks for that one, MS), but when you're dealing with potentially single byte data types that wouldn't work out so well.
Last edited on
Thank you for your response, but that's actually what I'm trying to avoid.

The reason that I am implementing NULL as the final parameter in the first place is because I want to be able to enter any number of arguments without having to explicitly state the number of arguments each time.

The point is for it to count each argument until it hits NULL, which should be the final argument. It's sort of a matter of convenience, considering I may end up using this function to choose upwards of 100 arguments in the future.

By implementing NULL as the final parameter, I've removed the need for stating the number of arguments as the first parameter, and it works perfectly, so long as a value equivalent to zero does not need to be a parameter.
That's actually a very good point, and it's given me an idea. Since the datatype of arg1 determines the datatype of all of the following arguments, perhaps I can create conditionals within the function to check the datatype being passed, and replace NULL with the maximum possible value of said datatype. I'm going to try that out. I'll let keep you updated on how it goes.

(Obviously this would result in me not being able to use the character 255, but I honestly don't see any circumstances where I would need to randomly choose it)
Last edited on
I am not sure that you need a delimter for your arg list.

http://en.wikipedia.org/wiki/Variadic_function#C.2C_Objective-C.2C_C.2B.2B.2C_and_D


This is the C++11 example from the above page:

1
2
3
4
5
6
7
8
void PrintSpaced(std::initializer_list<int> objects)
{
    for (int o : objects)
        std::cout << o << "\n";
}
 
// Can be used to print:
// PrintSpaced({1, 2, 3}); 


This does not seem to use a sentinel or delimiting value in the arg list.

This is the C++ example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename T>
void print( const T& t )
{
    std::cout << t;
}
 
template< typename T, typename... Args>
void print( const T& t, const Args&... args )
{
    print( t );
    print( args... );
}
 
template<typename... T>
class tuple;
template<typename T, typename... Ts>
class tuple<T,Ts...>;
template<>
class tuple<>;


Disclaimer: I don't really know what I am talking about - Just putting forward an idea that I suspect might be right. I hope it helps, or at least provides a clue. I am sure know there are others on this forum who know vastly more than me.
Well TheIdeasMan, I've been researching both of those examples to the best of my ability.
The second one seems to give me all sorts of syntax errors (visual c++ 2010 doesn't seem to like the "const Args&... args" being used as a parameter.
The first one works to a degree, but I get an error when I directly try to type something along the lines of:
funcName({1, 2, 3});

In any case, I would assume that I'm biting off more than I can chew with variable arguments, so I'm going to have to just settle for having it choose from an array for now, and come back to this when I have a more firm grasp on the concept.

If you're interested, here's the new, extremely simplified code:
1
2
3
4
5
6
7
#define ARR_LENGTH(a) (sizeof(a) / sizeof(a[0]))       //macro for number of elements in an array

template <typename T>
T choose (T args[], int length)                        //returns a random element from an array. The second argument should be ARR_LENGTH(args)
{
   return args[random.roll(length-1)];                 //returns a random element from args[]
}

Comparing it to the original code, it's a bit anticlimactic, but it serves its purpose. The only downside is that I now need to add all of the arguments to an array before they can be passed, but that's not too much of a hassle.

In any case, thank you both for your help as well as your time. You've given me some good ideas, and directed me to some areas that I obviously need to do some more research on.

Now, I'm fairly new to this forum (in that this was my first post), so one final question. Is this sufficient enough to mark this topic as solved, or should I leave it open?
Last edited on
(visual c++ 2010 doesn't seem to like the "const Args&... args" being used as a parameter.


I hope this is not a silly question, Do you have C++11 enabled on your complier?

It is probably not enabled by default, there will be a setting where you can specify compiler options to enable C++11.

Is this sufficient enough to mark this topic as solved, or should I leave it open?


The decision to mark a thread as solved is yours, but I would be inclined to leave it open, as there are others who might know much more about this, and could solve your problem properly.

Hope all goes well.
Topic archived. No new replies allowed.