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: 1. Design concept: Ways to improve? 2. Implementation: What weakness & strengths exist? --- 4 parts: 1. `main.c` to show a sample use. **`GPrintf()` must end in `GP_eol` or `NULL`** 2. Output 3. `GPrint.h` Key macro `GP()` 4. `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; } `main.c` 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](//http://stackoverflow.com/a/9736203/2410359)