This code allows printing in C with a style similar to C++ ostream
. By using _Generic()
(see GP()
) to form a string specifier, the compiler handles the selection of printf()
specifiers.
No more mis-matched specifiers!
Requested review goals:
Design concept: Ways to improve?
Implementation: What weakness & strengths exist?
4 parts:
main.c
to show a sample use.GPrintf()
must end inGP_eol
orNULL
Output
GPrint.h
Key macroGP()
GPrint.c
void GPrintf(const char *format, ...)
main.c
#include "GPrint.h"
#include <limits.h>
int main(void) {
double q = 1000000.0 / 7;
int i = 42;
GPrintf("42 --> base 10:", GP(i),
GP_setbase(2), " 2:", GP(i),
GP_setbase(36), " 36:", GP(i), GP_eol);
GPrintf("million/7 = ", GP(q), GP_eol);
GPrintf("million/7 = ", GP_setprecision(3), GP(q), GP_eol);
GPrintf("million/7 = ", GP_fixed, GP(q), GP_eol);
GPrintf("million/7 = ", GP_fixed, GP_setprecision(3), GP(q), GP_eol);
for (int y = 1; y < 9; y++) {
int x = y * y;
GPrintf(GP_setw(2), GP(y), ",", GP(x), ":", NULL);
GPrintf(GP_repeat(x), GP((char)'*'), GP_eol);
}
GPrintf(GP_setw(5), GP(" "), ":", NULL);
for (int x = 0; x < 70; x += 5)
GPrintf("----+", NULL);
GPrintf(GP_eol);
return 0;
}
Output
42 --> base 10:42 2:101010 36:16
million/7 = 1.4285714285714287e+05
million/7 = 1.429e+05
million/7 = 142857.1428571428696159
million/7 = 142857.143
1, 1:*
2, 4:****
3, 9:*********
4,16:****************
5,25:*************************
6,36:************************************
7,49:*************************************************
8,64:****************************************************************
:----+----+----+----+----+----+----+----+----+----+----+----+----+----+
GPrint.h
/*
* GPrint.c
*
* Created on: Dec 23, 2015
* Author: chux
*/
#ifndef GPRINT
#define GPRINT 1
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
typedef enum {
GP_none,
GP__Bool,
GP_char,
GP_signed_char,
GP_unsigned_char,
GP_short,
GP_unsigned_short,
GP_int,
GP_unsigned,
GP_long,
GP_unsigned_long,
GP_long_long,
GP_unsigned_long_long,
GP_float,
GP_double,
GP_long_double,
GP__Complex_float,
GP__Complex_double,
GP__Complex_long_double,
GP_void_star,
GP_char_star,
GP_const_char_star,
GP_eol_,
GP_FILE_,
GP_fixed_,
GP_flush_,
GP_repeat_,
GP_resetios_flags_,
GP_scientific_,
GP_set_base,
GP_set_precision,
GP_set_w,
GP_setios_flags_,
GP_string_,
// future showpos, uppercase, fixed, scientific, floatfield,
// showpoint, boolalpha, showbase,
GP_unknown_,
GP_format_N
} GP_type;
// format constants
const char GP_format[GP_format_N + 1][1];
// Functions to perform type conversions by the calling code
inline FILE *GP_check_FILE(FILE *f) {
return f;
}
inline int GP_check_int(int i) {
return i;
}
inline size_t GP_check_size_t(size_t sz) {
return sz;
}
inline char *GP_check_string(char *s) {
return s;
}
#undef inline
// utility functions (as macros)
#define GP_eol GP_format[GP_eol_]
#define GP_FILE(f) GP_format[GP_FILE_], (GP_check_FILE(f))
#define GP_fixed GP_format[GP_fixed_]
#define GP_repeat(n) GP_format[GP_repeat_], (GP_check_int(n))
#define GP_scientific GP_format[GP_scientific_]
#define GP_setbase(base) GP_format[GP_set_base], (GP_check_int(base))
#define GP_setprecision(pre) GP_format[GP_set_precision], (GP_check_int(pre))
#define GP_setw(width) GP_format[GP_set_w], (GP_check_int(width))
#define GP_string(s, sz) GP_format[GP_string_], (GP_check_string(s)),
// Macro to form (string, value) pair
#define GP(X) _Generic((X), \
_Bool: GP_format[GP__Bool], \
char: GP_format[GP_char], \
signed char: GP_format[GP_signed_char], \
unsigned char: GP_format[GP_unsigned_char], \
short: GP_format[GP_short], \
unsigned short: GP_format[GP_unsigned_short], \
int: GP_format[GP_int], \
unsigned: GP_format[GP_unsigned], \
long: GP_format[GP_long], \
unsigned long: GP_format[GP_unsigned_long], \
long long: GP_format[GP_long_long], \
unsigned long long: GP_format[GP_unsigned_long_long], \
float: GP_format[GP_float], \
double: GP_format[GP_double], \
long double: GP_format[GP_long_double], \
_Complex float: GP_format[GP__Complex_float], \
_Complex double: GP_format[GP__Complex_double], \
_Complex long double: GP_format[GP__Complex_long_double], \
void *: GP_format[GP_void_star], \
char *: GP_format[GP_char_star], \
const char *: GP_format[GP_const_char_star] \
),(X)
// THE function
void GPrintf(const char *format, ...);
#endif
GPrint.c
/*
* GPrint.c
*
* Created on: Dec 23, 2015
* Author: chux
*/
#include "GPrint.h"
#include <assert.h>
#include <float.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
struct GP_style {
char buf[sizeof(intmax_t) + 1 + 1];
// char sign[2][2];
int prec[3];
int width;
int repeat_m1;
unsigned base;
_Bool fixed;
};
static const char base36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Convert signed value to string depending on base
static char *utoa_base(struct GP_style *style, uintmax_t u) {
char *p = &style->buf[sizeof *(style->buf) - 1];
*p = '\0';
uintmax_t x = u;
do {
*(--p) = base36[x % style->base];
x /= style->base;
} while (x);
return p;
}
// Convert signed value to string depending on base
static char *itoa_base(struct GP_style *style, intmax_t i) {
char *p = &style->buf[sizeof *(style->buf) - 1];
*p = '\0';
intmax_t x = i < 0 ? i : -i;
do {
*(--p) = base36[-(x % style->base)];
x /= style->base;
} while (x);
if (i < 0) *(--p) = '-';
return p;
}
// Return index of GP_format[]
static int GP_get_type(const char *format) {
if (format >= GP_format[0] && format < GP_format[GP_format_N]) {
return (int) (format - GP_format[0]);
}
return GP_format_N;
}
// External ............................................
const char GP_format[GP_format_N + 1][1] = { { 0 } };
void GPrintf(const char *format, ...) {
va_list ap;
va_start(ap, format);
FILE *ostream = stdout;
struct GP_style style = { 0 };
style.prec[0] = FLT_DECIMAL_DIG - 1;
style.prec[1] = DBL_DECIMAL_DIG - 1;
style.prec[2] = LDBL_DECIMAL_DIG - 1;
while (format) {
switch (GP_get_type(format)) {
case GP__Bool:
fprintf(ostream, "%*d", style.width, (_Bool) va_arg(ap, int));
break;
case GP_char: {
char c = (char) va_arg(ap, int);
for (int i = -1; i < style.repeat_m1; i++) {
fprintf(ostream, "%*c", style.width, c);
}
break;
}
case GP_int: {
int i = va_arg(ap, int);
if (style.base == 0 || style.base == 10) {
fprintf(ostream, "%*d", style.width, i);
} else {
fprintf(ostream, "%*s", style.width, itoa_base(&style, i));
}
break;
}
case GP_unsigned: {
unsigned u = va_arg(ap, unsigned);
if (style.base == 0 || style.base == 10) {
fprintf(ostream, "%*u", style.width, u);
} else {
fprintf(ostream, "%*s", style.width, utoa_base(&style, u));
}
break;
}
case GP_float:
// (float) cast used to enforce float precision.
fprintf(ostream, style.fixed ? "%*.*f" : "%*.*e", style.width,
style.prec[0], (float) va_arg(ap, double));
break;
case GP_double:
fprintf(ostream, style.fixed ? "%*.*f" : "%*.*e", style.width,
style.prec[1], va_arg(ap, double));
break;
case GP_long_double:
fprintf(ostream, style.fixed ? "%*.*Lf" : "%*.*Le", style.width,
style.prec[2], va_arg(ap, long double));
break;
case GP_void_star:
fprintf(ostream, "%*p", style.width, va_arg(ap, void *));
break;
case GP_char_star:
fprintf(ostream, "%*s", style.width, va_arg(ap, char *));
break;
case GP_const_char_star:
fprintf(ostream, "%*s", style.width, va_arg(ap, char *));
break;
case GP_set_w:
style.width = va_arg(ap, int);
break;
case GP_set_precision:
style.prec[2] = style.prec[1] = style.prec[0] = va_arg(ap, int);
assert(style.prec[0] >= 0);
break;
case GP_set_base: {
int i = va_arg(ap, int);
assert(i >= 2 && i <= 36);
style.base = (unsigned) i;
break;
}
case GP_fixed_:
style.fixed = true;
break;
case GP_scientific_:
style.fixed = false;
break;
case GP_FILE_:
ostream = va_arg(ap, FILE *);
break;
case GP_repeat_:
style.repeat_m1 = va_arg(ap, int) - 1;
break;
case GP_eol_:
fputc('\n', ostream);
goto early_end;
case GP_flush_:
fflush(ostream);
break;
case GP_unknown_:
assert(0);
// This case is future code expansion
fputs("Unknown Type", ostream);
fflush(stdout);
goto early_end;
default:
if (format == NULL) goto early_end;
fputs(format, ostream);
break;
}
format = va_arg(ap, const char *);
}
early_end: va_end(ap);
}
Any printf()
replacement can certainly involves many lines of code. The above is only a portion, yet functional, part a full implementation. E.g. Printing to char *
not shown. _Complex
types not yet handled.
This approach is like-wise applicable to scanf()
.
I have not seen this usage of _Generic()
before. I came up with it over the past month.
Ref on _generic()
extensibility
float
,double
in different%a
precision (that code not implemented but easy to add). Did not consider detailing known pros and cons of the approach. Hmmm. \$\endgroup\$