Creating a function which accepts an unspecified number of argumentsUsing va_list, va_arg, va_start and va_endWhat should I know before following this tutorial?You should have at least a basic knowledge of stringstreams. Other than that, you only really need to know the basics.
So what will I get out of this tutorial?This tutorial is going to explain how you can pass an undefined number of arguments to a function. The code from this tutorial is going to be a basic version of sprintf, which is used to format a string, using tokens %s and %d. Sound fun? Read on!
So, all programs start with the header files that should be included. We're going to be using cstdarg, string, iostream, and sstream.
yeah, I know, quite a few. Don't worry though, they're really only for the example. To include those va macros, you need to include cstdarg.
Let's start:
cpp
#include <cstdarg> // for the macros va_list, va_arg, va_start and va_end
#include <iostream> // standard input/output stream
#include <string> // string data type
#include <sstream> // string stream
using namespace std;
So now we can get started on writing our function. Let's call it MYsprintf, since it will have a similar effect to that of
sprintf()cpp
string MYsprintf (const char *str, ...)
{
Let's go over what's going to be passed to the function. We have
const char *str which is basically the main string. Think of that of printf, it takes a string first, right? Same applies here.
After that, we have
... to which I guess your reaction is a little like this:

No worries, it basically means that we're going to be accepting more arguments, but we don't know what they are yet.

Now, the first thing we want to do is create a
va_list which is basically a list of arguments. It will store all those additional arguments that were passed to MYsprintf. We're also going to want to initialise that, using
va_start() like so:
cpp
va_list args;
va_start(args, str);
Next, I'm just going to create an
ostringstream so that we can add everything to a string.
cpp
ostringstream output;
The next thing that we need to do is loop through
str Nothing new here (or it shouldn't be) we just loop, like so:
cpp
for (unsigned int i = 0; i < strlen(str); i++)
{
Now, as we loop through
str we just need to format it. A little pseudocode:
CODE
if the current character is % and the following character is d or s
{
add it to the output stream
increment the counter to avoid adding % to the output string
}
else add the current character to the output string stream
Let's have a look at the code for the if statement:
cpp
if (str[i] == '%' && str[i+1] != '%' && i+1 < strlen(str)) // we need to format it
{
making sense so far? I hope so! Basically, we're making sure
i+1 is still in the scope of
str, and making sure that the character after '%' is not '%' (in which case, there's nothing to format)
Now, since we only accept s and d as parameters (string or int) we're gonna need a switch, too.
Let's take a look at the cases:
cpp
switch (str[i+1]) // switch the next character
{
case 's':
char *temp = va_arg (args, char*);
output << temp;
break;
case 'd':
int Temp = va_arg (args, int);
output << Temp;
break;
default:
output << str[i];
} // end switch
i++; // increment to avoid outputting the next character
} // end if
Pretty simple, the only new thing there should be
va_arg()Basically, we add our temporary value to args (our va_list) and then add it to the output string stream. The reason that we add it to args is to make sure that all arguments are accounted for by the end of the function.
After that, it's our else statement, and then the end of the function! So we're nearly there!

cpp
else output << str[i];
} // end for
Now all we need to do is clear up args, return the string, and exit the function, like so:
cpp
va_end(args);
return output.str();
} // end MYsprintf
And there we have it! Let's take a quick look at the implementation of MYsprintf:
cpp
int main ()
{
string myStr = MYsprintf ("%s is %d years old. %s", "gabehabe", 18, "(
)\"");
cout << myStr; // output the string
cin.get (); // hold the window open
return EXIT_SUCCESS; // successful execution
}
And here's the whole code:
cpp
#include <cstdarg> // for the macros va_list, va_start and va_end
#include <iostream> // standard input/output stream
#include <string> // string data type
#include <sstream> // string stream
using namespace std;
string MYsprintf (const char *str, ...)
{
va_list args;
va_start(args, str);
ostringstream output;
for (unsigned int i = 0; i < strlen(str); i++)
{
if (str[i] == '%' && str[i+1] != '%' && i+1 < strlen(str)) // we need to format it
{
switch (str[i+1]) // switch the next character
{
case 's':
char *temp = va_arg (args, char*);
output << temp;
break;
case 'd':
int Temp = va_arg (args, int);
output << Temp;
break;
default:
output << str[i];
} // end switch
i++; // increment to avoid outputting the next character
} // end if
else output << str[i];
} // end for
va_end(args);
return output.str();
} // end MYsprintf
int main ()
{
string myStr = MYsprintf ("%s is %d years old. %s", "gabehabe", 18, "(
.)\"");
cout << myStr; // output the string
cin.get (); // hold the window open
return EXIT_SUCCESS; // successful execution
}