Skip to content

Instantly share code, notes, and snippets.

@Eczbek
Last active June 15, 2026 20:15
Show Gist options
  • Select an option

  • Save Eczbek/534991ec341eb048e791ff44c1953a9f to your computer and use it in GitHub Desktop.

Select an option

Save Eczbek/534991ec341eb048e791ff44c1953a9f to your computer and use it in GitHub Desktop.
type-safe print
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#define DETAIL_EVAL(...) DETAIL_EVAL0(DETAIL_EVAL0(DETAIL_EVAL0(DETAIL_EVAL0(__VA_ARGS__))))
#define DETAIL_EVAL0(...) DETAIL_EVAL1(DETAIL_EVAL1(DETAIL_EVAL1(DETAIL_EVAL1(__VA_ARGS__))))
#define DETAIL_EVAL1(...) DETAIL_EVAL2(DETAIL_EVAL2(DETAIL_EVAL2(DETAIL_EVAL2(__VA_ARGS__))))
#define DETAIL_EVAL2(...) DETAIL_EVAL3(DETAIL_EVAL3(DETAIL_EVAL3(DETAIL_EVAL3(__VA_ARGS__))))
#define DETAIL_EVAL3(...) __VA_ARGS__
#define DETAIL_PARENS ()
#define DETAIL_EACH(F, ...) __VA_OPT__(DETAIL_EVAL(DETAIL_EACH_(F, __VA_ARGS__)))
#define DETAIL_EACH_(F, X, ...) F(X) __VA_OPT__(DETAIL_EACH_NEXT DETAIL_PARENS (F, __VA_ARGS__))
#define DETAIL_EACH_NEXT() DETAIL_EACH_
static inline const char* detail_escape(char c) {
switch (c) {
case '\a': return "\\a";
case '\b': return "\\b";
case '\t': return "\\t";
case '\n': return "\\n";
case '\v': return "\\v";
case '\f': return "\\f";
case '\r': return "\\r";
case ' ': return " ";
case '!': return "!";
case '"': return "\\\"";
case '#': return "#";
case '$': return "$";
case '%': return "%";
case '&': return "&";
case '\'': return "\\'";
case '(': return "(";
case ')': return ")";
case '*': return "*";
case '+': return "+";
case ',': return ",";
case '-': return "-";
case '.': return ".";
case '/': return "/";
case '0': return "0";
case '1': return "1";
case '2': return "2";
case '3': return "3";
case '4': return "4";
case '5': return "5";
case '6': return "6";
case '7': return "7";
case '8': return "8";
case '9': return "9";
case ':': return ":";
case ';': return ";";
case '<': return "<";
case '=': return "=";
case '>': return ">";
case '?': return "?";
case '@': return "@";
case 'A': return "A";
case 'B': return "B";
case 'C': return "C";
case 'D': return "D";
case 'E': return "E";
case 'F': return "F";
case 'G': return "G";
case 'H': return "H";
case 'I': return "I";
case 'J': return "J";
case 'K': return "K";
case 'L': return "L";
case 'M': return "M";
case 'N': return "N";
case 'O': return "O";
case 'P': return "P";
case 'Q': return "Q";
case 'R': return "R";
case 'S': return "S";
case 'T': return "T";
case 'U': return "U";
case 'V': return "V";
case 'W': return "W";
case 'X': return "X";
case 'Y': return "Y";
case 'Z': return "Z";
case '[': return "[";
case '\\': return "\\\\";
case ']': return "]";
case '^': return "^";
case '_': return "_";
case '`': return "`";
case 'a': return "a";
case 'b': return "b";
case 'c': return "c";
case 'd': return "d";
case 'e': return "e";
case 'f': return "f";
case 'g': return "g";
case 'h': return "h";
case 'i': return "i";
case 'j': return "j";
case 'k': return "k";
case 'l': return "l";
case 'm': return "m";
case 'n': return "n";
case 'o': return "o";
case 'p': return "p";
case 'q': return "q";
case 'r': return "r";
case 's': return "s";
case 't': return "t";
case 'u': return "u";
case 'v': return "v";
case 'w': return "w";
case 'x': return "x";
case 'y': return "y";
case 'z': return "z";
case '{': return "{";
case '|': return "|";
case '}': return "}";
case '~': return "~";
}
switch (c) {
case '\x0': return "\x0";
case '\x1': return "\x1";
case '\x2': return "\x2";
case '\x3': return "\x3";
case '\x4': return "\x4";
case '\x5': return "\x5";
case '\x6': return "\x6";
case '\x7': return "\x7";
case '\x8': return "\x8";
case '\x9': return "\x9";
case '\xA': return "\xA";
case '\xB': return "\xB";
case '\xC': return "\xC";
case '\xD': return "\xD";
case '\xE': return "\xE";
case '\xF': return "\xF";
case '\x10': return "\x10";
case '\x11': return "\x11";
case '\x12': return "\x12";
case '\x13': return "\x13";
case '\x14': return "\x14";
case '\x15': return "\x15";
case '\x16': return "\x16";
case '\x17': return "\x17";
case '\x18': return "\x18";
case '\x19': return "\x19";
case '\x1A': return "\x1A";
case '\x1B': return "\x1B";
case '\x1C': return "\x1C";
case '\x1D': return "\x1D";
case '\x1E': return "\x1E";
case '\x1F': return "\x1F";
case '\x20': return "\x20";
case '\x21': return "\x21";
case '\x22': return "\x22";
case '\x23': return "\x23";
case '\x24': return "\x24";
case '\x25': return "\x25";
case '\x26': return "\x26";
case '\x27': return "\x27";
case '\x28': return "\x28";
case '\x29': return "\x29";
case '\x2A': return "\x2A";
case '\x2B': return "\x2B";
case '\x2C': return "\x2C";
case '\x2D': return "\x2D";
case '\x2E': return "\x2E";
case '\x2F': return "\x2F";
case '\x30': return "\x30";
case '\x31': return "\x31";
case '\x32': return "\x32";
case '\x33': return "\x33";
case '\x34': return "\x34";
case '\x35': return "\x35";
case '\x36': return "\x36";
case '\x37': return "\x37";
case '\x38': return "\x38";
case '\x39': return "\x39";
case '\x3A': return "\x3A";
case '\x3B': return "\x3B";
case '\x3C': return "\x3C";
case '\x3D': return "\x3D";
case '\x3E': return "\x3E";
case '\x3F': return "\x3F";
case '\x40': return "\x40";
case '\x41': return "\x41";
case '\x42': return "\x42";
case '\x43': return "\x43";
case '\x44': return "\x44";
case '\x45': return "\x45";
case '\x46': return "\x46";
case '\x47': return "\x47";
case '\x48': return "\x48";
case '\x49': return "\x49";
case '\x4A': return "\x4A";
case '\x4B': return "\x4B";
case '\x4C': return "\x4C";
case '\x4D': return "\x4D";
case '\x4E': return "\x4E";
case '\x4F': return "\x4F";
case '\x50': return "\x50";
case '\x51': return "\x51";
case '\x52': return "\x52";
case '\x53': return "\x53";
case '\x54': return "\x54";
case '\x55': return "\x55";
case '\x56': return "\x56";
case '\x57': return "\x57";
case '\x58': return "\x58";
case '\x59': return "\x59";
case '\x5A': return "\x5A";
case '\x5B': return "\x5B";
case '\x5C': return "\x5C";
case '\x5D': return "\x5D";
case '\x5E': return "\x5E";
case '\x5F': return "\x5F";
case '\x60': return "\x60";
case '\x61': return "\x61";
case '\x62': return "\x62";
case '\x63': return "\x63";
case '\x64': return "\x64";
case '\x65': return "\x65";
case '\x66': return "\x66";
case '\x67': return "\x67";
case '\x68': return "\x68";
case '\x69': return "\x69";
case '\x6A': return "\x6A";
case '\x6B': return "\x6B";
case '\x6C': return "\x6C";
case '\x6D': return "\x6D";
case '\x6E': return "\x6E";
case '\x6F': return "\x6F";
case '\x70': return "\x70";
case '\x71': return "\x71";
case '\x72': return "\x72";
case '\x73': return "\x73";
case '\x74': return "\x74";
case '\x75': return "\x75";
case '\x76': return "\x76";
case '\x77': return "\x77";
case '\x78': return "\x78";
case '\x79': return "\x79";
case '\x7A': return "\x7A";
case '\x7B': return "\x7B";
case '\x7C': return "\x7C";
case '\x7D': return "\x7D";
case '\x7E': return "\x7E";
case '\x7F': return "\x7F";
case '\x80': return "\x80";
case '\x81': return "\x81";
case '\x82': return "\x82";
case '\x83': return "\x83";
case '\x84': return "\x84";
case '\x85': return "\x85";
case '\x86': return "\x86";
case '\x87': return "\x87";
case '\x88': return "\x88";
case '\x89': return "\x89";
case '\x8A': return "\x8A";
case '\x8B': return "\x8B";
case '\x8C': return "\x8C";
case '\x8D': return "\x8D";
case '\x8E': return "\x8E";
case '\x8F': return "\x8F";
case '\x90': return "\x90";
case '\x91': return "\x91";
case '\x92': return "\x92";
case '\x93': return "\x93";
case '\x94': return "\x94";
case '\x95': return "\x95";
case '\x96': return "\x96";
case '\x97': return "\x97";
case '\x98': return "\x98";
case '\x99': return "\x99";
case '\x9A': return "\x9A";
case '\x9B': return "\x9B";
case '\x9C': return "\x9C";
case '\x9D': return "\x9D";
case '\x9E': return "\x9E";
case '\x9F': return "\x9F";
case '\xA0': return "\xA0";
case '\xA1': return "\xA1";
case '\xA2': return "\xA2";
case '\xA3': return "\xA3";
case '\xA4': return "\xA4";
case '\xA5': return "\xA5";
case '\xA6': return "\xA6";
case '\xA7': return "\xA7";
case '\xA8': return "\xA8";
case '\xA9': return "\xA9";
case '\xAA': return "\xAA";
case '\xAB': return "\xAB";
case '\xAC': return "\xAC";
case '\xAD': return "\xAD";
case '\xAE': return "\xAE";
case '\xAF': return "\xAF";
case '\xB0': return "\xB0";
case '\xB1': return "\xB1";
case '\xB2': return "\xB2";
case '\xB3': return "\xB3";
case '\xB4': return "\xB4";
case '\xB5': return "\xB5";
case '\xB6': return "\xB6";
case '\xB7': return "\xB7";
case '\xB8': return "\xB8";
case '\xB9': return "\xB9";
case '\xBA': return "\xBA";
case '\xBB': return "\xBB";
case '\xBC': return "\xBC";
case '\xBD': return "\xBD";
case '\xBE': return "\xBE";
case '\xBF': return "\xBF";
case '\xC0': return "\xC0";
case '\xC1': return "\xC1";
case '\xC2': return "\xC2";
case '\xC3': return "\xC3";
case '\xC4': return "\xC4";
case '\xC5': return "\xC5";
case '\xC6': return "\xC6";
case '\xC7': return "\xC7";
case '\xC8': return "\xC8";
case '\xC9': return "\xC9";
case '\xCA': return "\xCA";
case '\xCB': return "\xCB";
case '\xCC': return "\xCC";
case '\xCD': return "\xCD";
case '\xCE': return "\xCE";
case '\xCF': return "\xCF";
case '\xD0': return "\xD0";
case '\xD1': return "\xD1";
case '\xD2': return "\xD2";
case '\xD3': return "\xD3";
case '\xD4': return "\xD4";
case '\xD5': return "\xD5";
case '\xD6': return "\xD6";
case '\xD7': return "\xD7";
case '\xD8': return "\xD8";
case '\xD9': return "\xD9";
case '\xDA': return "\xDA";
case '\xDB': return "\xDB";
case '\xDC': return "\xDC";
case '\xDD': return "\xDD";
case '\xDE': return "\xDE";
case '\xDF': return "\xDF";
case '\xE0': return "\xE0";
case '\xE1': return "\xE1";
case '\xE2': return "\xE2";
case '\xE3': return "\xE3";
case '\xE4': return "\xE4";
case '\xE5': return "\xE5";
case '\xE6': return "\xE6";
case '\xE7': return "\xE7";
case '\xE8': return "\xE8";
case '\xE9': return "\xE9";
case '\xEA': return "\xEA";
case '\xEB': return "\xEB";
case '\xEC': return "\xEC";
case '\xED': return "\xED";
case '\xEE': return "\xEE";
case '\xEF': return "\xEF";
case '\xF0': return "\xF0";
case '\xF1': return "\xF1";
case '\xF2': return "\xF2";
case '\xF3': return "\xF3";
case '\xF4': return "\xF4";
case '\xF5': return "\xF5";
case '\xF6': return "\xF6";
case '\xF7': return "\xF7";
case '\xF8': return "\xF8";
case '\xF9': return "\xF9";
case '\xFA': return "\xFA";
case '\xFB': return "\xFB";
case '\xFC': return "\xFC";
case '\xFD': return "\xFD";
case '\xFE': return "\xFE";
case '\xFF': return "\xFF";
}
return "";
}
enum {
detail_type_id_unsigned_char,
detail_type_id_unsigned_short,
detail_type_id_unsigned_int,
detail_type_id_unsigned_long,
detail_type_id_unsigned_long_long,
detail_type_id_signed_char,
detail_type_id_short,
detail_type_id_int,
detail_type_id_long,
detail_type_id_long_long,
detail_type_id_float,
detail_type_id_double,
detail_type_id_long_double,
detail_type_id_char,
detail_type_id_bool,
detail_type_id_string,
detail_type_id_address,
detail_type_id_unknown,
detail_type_id_sentinel
};
#define detail_type_id(X) \
_Generic((X), \
unsigned char: detail_type_id_unsigned_char, \
unsigned short: detail_type_id_unsigned_short, \
unsigned int: detail_type_id_unsigned_int, \
unsigned long: detail_type_id_unsigned_long, \
unsigned long long: detail_type_id_unsigned_long_long, \
signed char: detail_type_id_signed_char, \
short: detail_type_id_short, \
int: detail_type_id_int, \
long: detail_type_id_long, \
long long: detail_type_id_long_long, \
float: detail_type_id_float, \
double: detail_type_id_double, \
long double: detail_type_id_long_double, \
char: detail_type_id_char, \
bool: detail_type_id_bool, \
char*: detail_type_id_string, \
const char*: detail_type_id_string, \
void*: detail_type_id_address, \
const void*: detail_type_id_address, \
default: detail_type_id_unknown \
), (X),
static inline bool meta_print(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
for (size_t i = 0; fmt[i]; ++i) {
if ((!i || (fmt[i - 1] != '\\')) && (fmt[i] == '$')) {
switch (va_arg(args, int)) {
case detail_type_id_unsigned_char:
case detail_type_id_unsigned_short:
case detail_type_id_signed_char:
case detail_type_id_short:
case detail_type_id_int:
case detail_type_id_char:
case detail_type_id_bool:
va_arg(args, int);
continue;
case detail_type_id_unsigned_int:
va_arg(args, unsigned int);
continue;
case detail_type_id_unsigned_long:
va_arg(args, unsigned long);
continue;
case detail_type_id_unsigned_long_long:
va_arg(args, unsigned long long);
continue;
case detail_type_id_long:
va_arg(args, long long);
continue;
case detail_type_id_long_long:
va_arg(args, unsigned long long);
continue;
case detail_type_id_float:
case detail_type_id_double:
va_arg(args, double);
continue;
case detail_type_id_long_double:
va_arg(args, long double);
continue;
case detail_type_id_string:
va_arg(args, const char*);
continue;
case detail_type_id_address:
va_arg(args, void*);
continue;
case detail_type_id_unknown:
fprintf(stderr, "unprintable argument for placeholder at position %zu in format string ", i);
break;
case detail_type_id_sentinel:
fprintf(stderr, "no argument for placeholder at position %zu in format string ", i);
break;
}
fprintf(stderr, "\"");
while (*fmt) {
fputs(detail_escape(*fmt++), stderr);
}
fprintf(stderr, "\"\n");
va_end(args);
return false;
}
}
va_start(args, fmt);
bool escaped = false;
for (; *fmt; escaped = *fmt++ == '\\') {
if (escaped) {
if ((*fmt != '$') && (*fmt != '\\')) {
putc('\\', stdout);
}
putc(*fmt, stdout);
} else if (*fmt == '$') {
switch (va_arg(args, int)) {
case detail_type_id_unsigned_char:
case detail_type_id_unsigned_short:
fprintf(stdout, "%u", (unsigned int)va_arg(args, int));
continue;
case detail_type_id_unsigned_int:
fprintf(stdout, "%u", va_arg(args, unsigned int));
continue;
case detail_type_id_unsigned_long:
fprintf(stdout, "%lu", va_arg(args, unsigned long));
continue;
case detail_type_id_unsigned_long_long:
fprintf(stdout, "%llu", va_arg(args, unsigned long long));
continue;
case detail_type_id_signed_char:
case detail_type_id_short:
case detail_type_id_int:
fprintf(stdout, "%i", va_arg(args, int));
continue;
case detail_type_id_long:
fprintf(stdout, "%li", va_arg(args, long));
continue;
case detail_type_id_long_long:
fprintf(stdout, "%lli", va_arg(args, long long));
continue;
case detail_type_id_float:
case detail_type_id_double:
fprintf(stdout, "%lf", va_arg(args, double));
continue;
case detail_type_id_long_double:
fprintf(stdout, "%Lf", va_arg(args, long double));
continue;
case detail_type_id_char:
fprintf(stdout, "%c", va_arg(args, int));
continue;
case detail_type_id_bool:
fprintf(stdout, "%s", va_arg(args, int) ? "true" : "false");
continue;
case detail_type_id_string:
fprintf(stdout, "%s", va_arg(args, const char*));
continue;
case detail_type_id_address:
fprintf(stdout, "%p", va_arg(args, void*));
continue;
}
} else if (*fmt != '\\') {
putc(*fmt, stdout);
}
}
if (escaped) {
putc('\\', stdout);
}
va_end(args);
return true;
}
#define meta_print(FMT, ...) meta_print((FMT), DETAIL_EACH(detail_type_id, __VA_ARGS__) detail_type_id_sentinel)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment