Format String with Variadic Arguments

Format string with variadic arguments

String has two similar initializers:

init(format: String, _ arguments: CVarArg...)
init(format: String, arguments: [CVarArg])

The first one takes a varying number of arguments, the second one
an array with all arguments:

print(String(format: "x=%d, y=%d", 1, 2))
print(String(format: "x=%d, y=%d", arguments: [1, 2]))

In your localized method, args: CVarArg... is a variadic parameter
and those are made available within the functions body as an array
of the appropriated type, in this case [CVarArg].
Therefore it must be passed to String(format: arguments:):

func localized(table: String? = nil, bundle: Bundle = .main, args: CVarArg...) -> String {
return String(
format: NSLocalizedString(
self,
tableName: table,
bundle: bundle,
value: self,
comment: ""
),
arguments: args // <--- HERE
)
}

See also "Variadic Parameters" in the "Functions" chapter of the
Swift reference.

Implement variadic arguments checking for custom string formatting functions

So it seems that I'm indeed out of luck with Visual Studio.

As mentioned by Jonathan in his answer, it is possible to do this with both GCC and Clang. This is also explained in this answer.

However, while Visual Studio seems to output warnings for printf and a bunch of other standard functions, this is more or less hard-coded in the compiler and is not extensible to custom functions.

There is an alternative tho, which I decided not to use (I will explain why). Microsoft provides what they call SAL annotation (for source code annotation language). It is possible to annotate a function with things like _Printf_format_string_ in order to get what I was asking. This is described in this answer for example.

The drawback is, this is by default totally ignored by the compiler. These annotations are actually evaluated only if you enable the Code Analysis, either with the /analysis parameter or from the property window of your project. This analysis does a lot of checks; by default it uses the Microsoft Native Recommended Rules, but it's possible to customize what is to be checked, and even go down to only checking for string formatting.

But even to that point, the overhead in compilation time, for a relatively small project like mine, makes it not worth the pain.

How to pass variadic function parameters to another function in C++ (specifically std::format)?

You don't want the C-style ... parameter. You want a variadic template:

template <typename T, typename ...P>
void log(T &&format, P &&... params)
{
std::string msg = fmt::format(std::forward<T>(format), std::forward<P>(params)...);
std::cout << msg << '\n';
}

Notice forwarding references for the parameter, pack instead of const references. Const references had caused problems for me before (spdlog used to use const references, and fmt::join didn't like them).

Notice the first parameter being templated. At least with libfmt, this is necessary to be able to receive FMT_STRING(...) parameters. With std::format, an std::string_view would probably suffice. const std::string & is uncool because it forces a heap allocation.

Also I'm not sure how I feel about having a separate parameter-less overload. It means that you have to escape { and } only when there's at least one parameter, which is not nice.

Also consider using spdlog, which is very similar. It wraps libfmt, and can direct output to various/arbirary targets.

Creating a format string with a variable number of specifiers

As far as I know, it is not possible to do that. If you are not so keen about using variadic functions and can redefine the function. The below code suits your need; Iterate through each item in the array and append to the string using snprintf.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXBUF 4096
#define SPECIFIER "(%s)"

char *strmaker(int num_args, char** strings)
{
char *prnt = (char *) malloc(sizeof(char) * MAXBUF);
int cur = 0;

/* Append the strings to the prnt buffer */

for (int i = 0; i < num_args; i++) {
int p_return = snprintf(prnt + cur, MAXBUF - cur, SPECIFIER, strings[i]); // If no error, return the number characters printed excluding nul (man page)

if (p_return >= MAXBUF - cur) // If buffer overflows (man page)
return prnt;

cur = cur + p_return; // Update the index location.
}

return prnt;
}

int main(int argc, char *argv[])
{
if (argc <= 1)
return -1;

char *s = strmaker(argc - 1, argv + 1);
printf("%s\n", s);
free(s);

return 0;
}

Terminal Session:

$ ./a.out 1 2 3 
(1)(2)(3)
$ ./a.out 1 2 3 4 5 6 7
(1)(2)(3)(4)(5)(6)(7)
$ ./a.out Hello, This is stackoverflow, Bye
(Hello,)(This)(is)(stackoverflow,)(Bye)

Printing a variable number of arguments to a format string

As others said there is no direct way of doing that. You can build your own function which dumps the values of strings at the correct format specifiers. Below function makes a temporary format string for each %s and appends it to the earlier build string using snprintf().

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXBUF 4096

char *strmaker(char* format, int num_args, char** strings)
{
char* prnt = calloc(sizeof(char), MAXBUF);
int prnt_ct = 0;
char* tmp_fmt = malloc(strlen(format) + 1); // Prepare for the worst case (format == tmp_fmt).
int fmt_ct = 0;

/* Append the strings to the prnt buffer */

for (int i = 0; i < num_args; i++) {
char* s_loc = strstr(format + fmt_ct, "%s"); // Search the format-string for string specifier (%s)
if (s_loc == NULL)
return prnt;

int tmp_fmt_len = (int) (s_loc + 2 - format - fmt_ct); // +2 for %s
strncpy(tmp_fmt, format + fmt_ct, tmp_fmt_len); // Make tmp_fmt
tmp_fmt[tmp_fmt_len] = '\0';
fmt_ct = fmt_ct + tmp_fmt_len;

int p_return = snprintf(prnt + prnt_ct, MAXBUF - prnt_ct, tmp_fmt, strings[i]); // If no error, return the number characters printed excluding nul (man page)

if (p_return >= MAXBUF - prnt_ct) // If buffer overflows (man page)
return prnt;

prnt_ct = prnt_ct + p_return; // Update the index location.
}

return prnt;
}

int main(int argc, char *argv[]) // Pass format and arguments
{
if (argc <= 1)
return -1;

char *s = strmaker(argv[1], argc - 2, argv + 2);
printf("%s\n", s);
free(s);

return 0;
}

Terminal Session:

$ ./a.out '%s %s %s' 1 2 3 
1 2 3
$ ./a.out 'one %s two %s three %s' 1 2 3
one 1 two 2 three 3
$ ./a.out 'one %s two %s three' 1 2 3
one 1 two 2
$ ./a.out 'one %s two %s three %s' 1 2
one 1 two 2

String formatting with variable number of arguments in java

Try:

String formatted = String.format(str, args.toArray());

This gives:

Entered number = 1 and string = abcd

How to format variable number of arguments?

You could join all your templates using a character that you're unlikely to see in the templates or arguments, do the string interpolation, and then split the result.

templates = [
"{} one",
"none",
"{} two {}",
"{} {} three {}",
"one2 {}",
"and {{literal}} braces {{}}"
]
arguments = ["a", "b", "c", "d", "e", "f", "g"]

joined_template = chr(1).join(templates)

formatted_string = joined_template.format(*arguments)

formatted_templates = formatted_string.split(chr(1))

formatted_templates is now:

['a one',
'none',
'b two c',
'd e three f',
'one2 g',
'and {literal} braces {}']


Related Topics



Leave a reply



Submit