'Could you write a printf wrapper that interleaves formatting code and arguments?
I like the succinct format specifiers that printf offers such as %1.3f, as compared to cout, but hate how you have to put all the variables you are going to print at the end of the argument list, which is prone to errors whenever you want to add a new item to be printed. Here's what I'd like to do. Instead of
printf("\n\n%ix%i%c %1.2f\n",sw, sh, interlaced, refresh);
I'd like
printf("\n\n%i", sw, "x%i", sh, "%c ", interlaced, "%1.2f\n", refresh);
It seems like this should be possible with the right wrapper function, but writing variable argument functions is a black art to me. Or has somebody already written this? It seems like such an obvious idea.
Solution 1:[1]
Easy:
printf("\n\n");
printf("%i", sw);
printf("x%i", sh);
printf("%c ", interlaced);
printf("%1.2f", refresh);
printf("\n");
Solution 2:[2]
In case you are using C++, you could potentially do this like:
template<typename ... Args, std::size_t ... N>
void myPrint_impl(std::tuple<Args...> tup, std::index_sequence<N...>)
{
(std::printf(std::get<N * 2>(tup), std::get<N * 2 + 1>(tup)), ...);
}
template<typename ... Args>
void myPrint(Args ... args)
{
return myPrint_impl(std::make_tuple(args...), std::make_index_sequence<sizeof...(args) / 2>{});
}
int main()
{
myPrint("\n\n%i", 10, "x%i", 20, "%c ", 'o', "%1.2f\n", 1.234);
}
Solution 3:[3]
For a strictly C answer, yes you can, but people don’t because the problem becomes when do the arguments end?
A normal format specifier string matches specifiers in both number and type to the arguments that follow. When there are no more specifiers, any remaining arguments (should there be any) are simply ignored.
But if you want to interleave, then you must have a terminal argument, such as NULL, in order to signal end-of-arguments. Your interleaved printf would then look like this:
iprintf( "%d", my_int, "%s", "my string", "%f", my_float, NULL );
That last NULL argument is the thorn that people don’t like, in large part because it can be accidentally forgotten, leading to UB code!
Consequently, I will not present a solution for the above syntax.
Evil Macros to the Rescue
Yes, we’re not done. It is entirely possible to create the syntax you like by abusing the C preprocessor! The good news is that this also solves the UB problem with non-terminal argument lists.
The bad news is that it takes a lot of LOC to make it happen: specifically, you must declare your magic function and then override its declaration with macros, which is an evil thing to do, as the macros have no awareness of context and could clobber something totally unrelated.
(It will take me a few minutes to design and test some code for you.)
EDIT: heh, actually, I really don’t want to rewrite printf tonight... (because that is what is needed, basically). In other words, this weird syntax can be done, but no one wants to. I don’t.
Meh, evil macros + first solution for the win!
So, this question has suddenly become very popular, it seems. IDK why anyone would want to do this, but here you go: multi_printf and family:
// ABSOLUTELY NOT FULLY TESTED!
// NOT FOR PRODUCTION CODE!
// (Why would you do this anyway?)
#include <errno.h>
#include <iso646.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
int multi_vsnprintf(
char * buffer,
size_t size,
const char * format,
va_list args )
{
int result = 0;
while (format and ((size-result > 0) or !buffer))
{
// This is the easy part: make the C standard library do all the work
va_list args2;
va_copy( args2, args );
int n = vsnprintf( buffer ? buffer+result : NULL, buffer ? size-result : 0, format, args2 );
va_end( args2 );
// If anything went wrong we need to quit now to propagate the error result upward,
// otherwise we just keep accumulating the number of characters output
if (n < 0)
{
result = n;
break;
}
result += n;
// Now for the obnoxious part: skip args to find the next format string
// This only understands standard C99 argument types and modifiers!
// (POSIX throws some others in there too, methinks, etc. We ignore them.)
for (const char * p = strchr( format, '%' ); p and *p; p = strchr( p+1, '%' )) do
{
p = strpbrk( p+1, "%*csdioxXufFeEaAgGnp" );
switch (*p)
{
case '%':
break;
case '*':
va_arg( args, int );
continue;
case 'c': //if (p[-1] == 'l') va_arg( args, wint_t ); else va_arg( args, int ); break;
va_arg( args, int );
break;
case 's': if (p[-1] == 'l') va_arg( args, char * ); else va_arg( args, wchar_t * ); break;
case 'd': case 'i': case 'o': case 'x': case 'X': case 'u':
switch (p[-1])
{
case 'l': if (p[-2] == 'l') va_arg( args, long long );
else va_arg( args, long ); break;
case 'j': va_arg( args, intmax_t ); break;
case 'z': va_arg( args, size_t ); break;
case 't': va_arg( args, ptrdiff_t ); break;
default: va_arg( args, int ); break;
}
break;
case 'f': case 'F': case 'e': case 'E': case 'a': case 'A': case 'g': case 'G':
if (p[-1] == 'L') va_arg( args, long double );
else va_arg( args, double ); // 'l' and (none)
break;
case 'n': case 'p':
va_arg( args, void * ); // all are pointer types
break;
default:
p = NULL;
result = -1;
errno = EINVAL; // Invalid Argument
break;
}
} while (0);
format = va_arg( args, const char * );
}
return result;
}
int multi_vfprintf(
FILE * stream,
const char * format,
va_list args )
{
// In order to print to file we must first print to string
// (1) get the length of the needed string
va_list args2;
va_copy( args2, args );
int result = multi_vsnprintf( NULL, 0, format, args2 );
va_end( args2 );
// (2) print to the string then print the string to file
if (result > 0)
{
char * s = (char *)malloc( result+1 );
if (!s) result = -1;
else if (multi_vsnprintf( s, result+1, format, args ) > 0)
{
result = fprintf( stream, "%s", s );
}
free( s );
}
return result;
}
#define multi_vprintf(format,args) multi_vfprintf( stdout, format, args )
#define multi_vsprintf(buffer,format,args) multi_vsnprintf( buffer, SIZE_MAX, format, args )
int multi_snprintf(
char * buffer,
size_t size,
const char * format,
... )
{
va_list args;
va_start( args, format );
int result = multi_vsnprintf( buffer, size, format, args );
va_end( args );
return result;
}
int multi_fprintf(
FILE * stream,
const char * format,
... )
{
va_list args;
va_start( args, format );
int result = multi_vfprintf( stream, format, args );
va_end( args );
return result;
}
#define multi_printf(format,...) multi_fprintf( stdout, format, __VA_ARGS__, NULL )
#define multi_fprintf(stream,format,...) multi_fprintf( stream, format, __VA_ARGS__, NULL )
#define multi_sprintf(buffer,format,...) multi_snprintf( buffer, SIZE_MAX, format, __VA_ARGS__, NULL )
#define multi_snprintf(buffer,size,format,...) multi_snprintf( buffer, size, format, __VA_ARGS__, NULL )
int main()
{
const char * s = "worlds alive";
int n = multi_printf(
//format string //arguments
"%s ", "Hello",
"%.5s%c", s, '!',
" -no-specs- ",
"%d", 17,
" %f\n", 3.14159265
);
for (int k = 0; k < n; k++) printf( "-" );
printf( "%d characters printed\n", n );
}
Caveats
Both of these solutions have a significant drawback: C compilers have code in them to check that printf-family functions have the correct number and type of arguments to match the format argument.
Neither of the above versions can do that. So if, for example, you mis-match the types of arguments, you are SOL.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Ranoiaetep |
| Solution 3 |
