DEV Community

How do I use variable-arguments (varargs) in C++ (effectively)?

Calin Baenen on December 04, 2021

So, C++ extends C, and as that being the case, the implementation of varargs persisted (and hasn't changed, since it could break already existing c...
Collapse
 
omercsp profile image
Omer • Edited

Err... why do you think "most of C's original .h files are deprecated"? Many C++ application are using very old C functions and macros. Sometimes the C++ features (e.g. STL) provide better alternatives. and sometimes... well, sometimes it's just better to use old axes that are sharp enough.

In general, you can include "stdarg.h" as you used to in C (like any other C header), and most of the time it will work. The better and recommended approach when it comes to standard C headers, is to include the C++ wrappers for them so everything is compatible with C++. These headers are named "cHEADER" instead of "HEADER.h". For example instead of including <stdlib.h> you would include <cstdlib>. The same rule applies for "stdarg.h". Include <cstdarg> (note: no ".h" suffix!), and go on with you life.

Collapse
 
calinzbaenen profile image
Calin Baenen • Edited

Do C++'s wrappers add support for C++ types?
E.g. you could format in std::string using sprintf in <cstdio>?
Just tested in SoloLearn's C++ code playground, turns out you can't.

Also, do things get placed into the std (or any other) namespace (as to not pollute the global NS)?
Yes.

(Because funnily enough, string formatting is the whole reason I'm here. - Trying to make my own sprintf -- for std::strings.)

Collapse
 
omercsp profile image
Omer • Edited

Let's break it down a bit, because you are mixing a couple of questions here (and ignoring the original one):

a. Regarding std::string usage in the printf functions family - All the stdio vararg functions (printf, sprintf, snprintf, etc) are old C functions. They have no knowledge (and can't have any) of C++ objects and such. This doesn't mean you can't use them in sprintf (or printf, or whatever). In particular std::string implements a nice function named c_str that returns a c-style string (i.e. const char*) of the string object. This is exactly what you need to for using it in and Xprintf function. For example, this snippet:

std::string s = "If I had wings, I wouldn't do anything beautiful and transcendent";
printf("This is my string - %s\n", s.c_str());
Enter fullscreen mode Exit fullscreen mode

Will work as you expect.

b. Now, the other question is - what should you use for string formatting in C++? Well, you can go either way, up to you and what your are trying to do:

  • First, never use sprintf. If you are going down the (valid and beautiful) road of 'using some C functions' always use snprintf to avoid overflows. As we saw above, it works.

  • Second, C++ STL has some nice facilities to give you the parse-things-into-a-buffer mechanism you need. In particular, the std::stringstream:

  std::stringstream sstr;
  std::string s = "No, I'd get my finger into everything I wanted";
  sstr << s << ", and here are some numbers " << 5 << " " << 7;
  std::cout <<sstr.str() << std::endl;
Enter fullscreen mode Exit fullscreen mode

Guess what. That would also work. It knows how to take pretty much everything C++ native, and if you know what you're doing (mostly, implementing << operators for your own types), you can extend it to take whatever you want.

Which one should you use? Well, up to you. Both are fine. It's frequently claimed the C variants are faster and easier to read and understand. The C++ way is more generic, and should probably be preferred if you need multiple format calls, as there's a object (the std::stringstream you would create) you can refer to. You better get yourself familiar with both of them and choose the right tool for what it is you are trying to do (always a good idea).

BTW, you do know can concatenate strings with +, right? If all you do is putting strings together, this will be the easiest way.

Please note that this is a very different question than what you had asked in the first place. From "what do to instead of varargs?", (Nothing. Use varargs) we moved to "Is there a C++ way to do what sprintf does?". When asking for help, be concise, and ask about the "real" problem, not about the layer(s) above it. The solution is usually there.

Thread Thread
 
calinzbaenen profile image
Calin Baenen

Please note that this is a very different question than what you had asked in the first place. From "what do to instead of varargs?", (Nothing. Use varargs) to "Is there a C++ way to do what sprintf does?". When asking for help, be concise, and ask about the "real" problem, not about the layer(s) above it. The solution is usually there.

I asked because the question was relevant to why I made my post, and because you said most of C's files are valid in C++.
This prompted me to ask because all I want string formatting, but it seems if I want anything done right in C++, I have to do it myself.

std::string s = "If I had wings, I wouldn't do anything beautiful and transcendent";
printf("This is my string - %s\n", s.c_str());

(Why does the above code work w/o std::? - Can C++ resolve identifiers if there aren't multiple w/ the same name in diff namespaces?)

I need to capture the string here, which I could technically do, since std::string(char const*) constructor exists.
But, there's a problem here, I one, need to provide my own memory for the C-string provided to the first argument (I don't know if this applies to the var-args), which means I can't do char* b = "Hello, %s!"; sprintf(/* ... */);.
On top of that, I also have my format string saved in an std::string, and as I've tested, passing in std::string.c_str() (char const*) to sprintf is apparently a no-no (invalid conversion from char* to const char*).

Second, C++ STL has some nice facilities to give you the parse-things-into-a-buffer mechanism you need. In particular, the std::stringstream:

  std::stringstream sstr;
  std::string s = "No, I'd get my finger into everything I wanted";
  sstr << s << ", and here are some numbers " << 5 << " " << 7;
  std::cout <<sstr.str() << std::endl;

I've seen (and used) this before. I don't like it, plus, it's absolutely NOT what I want.
I want to be able to pass a string with any string formatters into a function, ans pass in a variable number of arguments, and get a formatted string returned. With this method, I have a static amount of inputs for the format (basically, the opposite of a variable amount*).

You better get yourself familiar with both of them and choose the right tool for what it is you are trying to do (alway a good idea).

At this point, I might as well figure out how to make it myself (which I was, hence the question about varargs in C++).
Since apparently C++ is shit at providing actual format-strings.

But seriously. Neither of these tools are the ones for me.

BTW, you do know can concatinate strings with +, right? If all you do is puting strings together, this will be the easyest way.

I could. But adding strings is not the same as formatting.
And funnily enough, this is EVEN WORSE than stringstream, because there's only support for concatenating other strings to it, and not numbers.
But concatenation isn't what I want in the first place.

Thread Thread
 
omercsp profile image
Omer • Edited

I apologize for criticizing the way you ask questions, but it's important. Knowing how to ask the right questions is an important tool for a developer. The fact is - you asked a question, and that is what you got an answer for. Then you asked another question, and got an answer for that one too. Now , on the third iteration, you ask another different question (still not clear, though).

Dude, this is not how you get work done.

Regardless:

if I want anything done right in C++, I have to do it myself

and

At this point, I might as well figure out how to make it myself (which I was, hence the question about varargs in C++).
Since apparently C++ is shit at providing actual format-strings.

Usually wrong. For C++, as well of most of the mature languages. If a language provides you the facility to do something, you need a very very good reason to not use it for the same task. So far, no such reason was provided.

I need to capture the string here, which I could technically do, since std::string(char const*) constructor exists.
But, there's a problem here, I one, need to provide my own memory for the C-string provided to the first argument (I don't know if this applies to the var-args), which means I can't do char* b = "Hello, %s!"; sprintf(/* ... */);.
On top of that, I also have my format string saved in an std::string, and as I've tested, passing in std::string.c_str() (char const*) to sprintf is apparently a no-no (invalid conversion from char* to const char*).

It looks like you got the sprintf arguments somewhat mixed up. This is how sprintf is declared (cleaned up a bit for clarity):

int sprintf (char *buff, const char *format, ...);

  • 1st argument is the target buffer, which is required to be mutable. So you must pass it a char array. For this, argument, std::string will not work. You need a proper char based buffer.

  • The 2nd argument, the format, is const char*, which means a std::string::c_strwill be perfect for it. Hence, code like this:

    char buff[256];
    std::string fmt = "%s%d";
    std::sprintf(buff, fmt.c_str(), "abc", 5);
    std::cout << buff << std::endl;
Enter fullscreen mode Exit fullscreen mode

is fine.

This has nothing to do with var-args. It's just the way this function is defined.

[sprintf is used here but NEVER usesprintf. ALWAYS use snprintf)]

I want to be able to pass a string with any string formatters into a function, ans pass in a variable number of arguments, and get a formatted string returned. With this method, I have a static amount of inputs for the format (basically, the opposite of a variable amount*).

This is probably the closest you got to describe your real problem (though still pretty vague, some code will probably help). If I got it right this time, you can find some answers here: stackoverflow.com/questions/234216.... Note the C++11 solution in the first answer formats the string twice, so it's robust and correct, but not very efficient in terms runtime(not a problem unless you write time sensitive application).

You can see that answers given there (unless you use C++20), are around the concepts I layered out during this discussion. You can dislike it all you want, but it's like that for a reason. Reinventing the wheel will usually give you grief, not joy.

And regarding your side question:

(Why does the above code work w/o std::? - Can C++ resolve identifiers if there aren't multiple w/ the same name in diff namespaces?)

Roughly speaking, C++ is a superset of C, thus any valid C is valid C++ code. Since printf(...() (without the std:: prefix) is valid C, any C++ compiler should be able to build it. In reality, there are some corner cases C code fails a C++ build, but 99% of the time, it's fine. In gcc 10, for example, printf and its friends defined within the std namespace following using statements for them (using std::printf and so), so you get the printf in the global namespace for free.

Thread Thread
 
calinzbaenen profile image
Calin Baenen

I apologize for criticizing the way you ask questions, but it's important. Knowing how to ask the right questions is an important tool for a developer.

Eh, criticism isn't something I treat with negativity, thanks for pointing out the flaw I only partially recognize.
Sorry for the "question hopping".

I'd say I got a bit carried away with trying to find a "best" way of string-formatting, and as of C++20, I found it std::format -- That's essentially everything I want and all I need.

Thread Thread
 
calinzbaenen profile image
Calin Baenen

I'd say I got a bit carried away with trying to find a "best" way of string-formatting, and as of C++20, I found it std::format -- That's essentially everything I want and all I need.

I tried looking for how to format strings in C++ before, but this never really came up to surface (I use DuckDuckGo). All I get is stringstream and snprintf/printf solutions.

Collapse
 
calinzbaenen profile image
Calin Baenen

It's a little late, but. Thanks for the reply.

I'm glad to be informed some (if not most) of C's libraries are still good to use.

Collapse
 
calinzbaenen profile image
Calin Baenen

@omercsp Since I got tread-locked, I thought I'd leave my response to a good point you made in this reply.

If a language provides you the facility to do something, you need a very very good reason to not use it for the same task. So far, no such reason was provided.

Excluding snprintf, C++ only has one way of doing "formatting", stringstreams.

std::stringstream strs;
std::string str  = "My Str";
strs << s << "ing.";
strs.str(); // "My String.". - This was concatenation.
Enter fullscreen mode Exit fullscreen mode

This solution works well with a finite number of arguments, but when you need an unpredictable or changing amount; it gets hard to manage.
One may decide to make multiple function overloads for them to format their string, but this bloats the code, and requiring lots of overloads will make things harder to manage.
A negligible nitpick is that it looks bulky. - I heard it described as verbose. But, it's not. It's just bulky.

snprintf definitely is the way to go.

My opinion is just that C++ swung and missed here.

Collapse
 
pgradot profile image
Pierre Gradot

Since C++20, you have std::format() en.cppreference.com/w/cpp/utility/...

You can also you the great fmt library fmt.dev/latest/index.html

Collapse
 
calinzbaenen profile image
Calin Baenen • Edited

Since C++20, you have std::format() en.cppreference.com/w/cpp/utility/...

I tried. I don't.
G++ (11.1.0 (GCC)) on Arch Linux (x86_64) says the format header does not exist.

I'm not aware of how to install the format header.

Thread Thread
 
pgradot profile image
Pierre Gradot

en.cppreference.com/w/cpp/compiler... : it seems that no compiler has support for at the moment...

std::format() is just a function that you have in the fmt library. I believe everybody keeps using it ^^

Collapse
 
yekyam profile image
Manuel Mateo

C++ has the concept of variadic templates, which are just like C's variadic functions, just less of a hassle.

Collapse
 
calinzbaenen profile image
Calin Baenen

just less of a hassle.

Less... but it comes with its own caveat, in order to use it, you must have one parameter of generic-type T, before ...Args (if you want to do anything useful with the type info).

Another let-down is that you can't pass void to T (if you have a parameter that is of type T), which means you must have some extra bit of data OR have an overload for your function that only takes the parameters before T.
For my case of string formatting, this is redundant because I'd literally just be returning the same string passed in.

But over-all, yes. Variadic templates are something I adore. And I wish Java had them (then maybe I would have stuck around for a little longer).

Collapse
 
yekyam profile image
Manuel Mateo

Doesn't Vargs also need to have at least one argument before the parameter pack too?

Could you expand on your second point?