1 comment

[ 3.8 ms ] story [ 15.7 ms ] thread
GCC's printf annotation can give you compile-time safety.

You can get run-time type checking in C using C99 variadic macros and C11 _Generic expressions. Here's a proof-of-concept I threw together earlier this year in a moment of boredom. I only tested it with a few versions of clang and GCC.

  #include <assert.h>
  #include <errno.h>
  #include <stdarg.h> /* va_list va_arg va_end va_start */
  #include <stdio.h>
  #include <string.h>
  
  #include <netinet/in.h>
  
  #define STRFMT_CALL(F, ...)  F(__VA_ARGS__)
  #define STRFMT_NARG_(a, b, c, d, e, f, g, h, i, j, N,...) N
  #define STRFMT_NARG(...) STRFMT_NARG_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
  #define STRFMT_PASTE(x, y)   x##y
  #define STRFMT_XPASTE(x, y)  STRFMT_PASTE(x, y)
  #define STRFMT_STRINGIFY_(s) #s
  #define STRFMT_STRINGIFY(s)  STRFMT_STRINGIFY_(s)
  
  enum strfmt_type {
    STRFMT_NONE = 0,
    STRFMT_INT,
    STRFMT_UINT,
    STRFMT_LONG,
    STRFMT_ULONG,
    STRFMT_LLONG,
    STRFMT_ULLONG,
    STRFMT_FLOAT,
    STRFMT_DOUBLE,
    STRFMT_LDOUBLE,
    STRFMT_CHAR_P,
    STRFMT_SCHAR_P,
    STRFMT_UCHAR_P,
    STRFMT_VOID_P,
    STRFMT_STRUCT_IN_ADDR_P,
    STRFMT_STRUCT_IN6_ADDR_P,
    STRFMT_END_P,
  };
  
  #define STRFMT_END (&(struct strfmt_end){ 0 })
  struct strfmt_end { int _; };
  
  extern int strfmt_unknown_expression_type;
  #define strfmt_badtype(X) strfmt_unknown_expression_type
  
  #define strfmt_promote(X) ((0)? 0 : (X))
  
  #define strfmt_typeof_p_g(X, Q, def)                             \
    _Generic(strfmt_promote(X),                              \
        Q char *: STRFMT_CHAR_P,                             \
        Q signed char *: STRFMT_SCHAR_P,                     \
        Q unsigned char *: STRFMT_UCHAR_P,                   \
        Q void *: STRFMT_VOID_P,                             \
        Q struct in_addr *: STRFMT_STRUCT_IN_ADDR_P,         \
        Q struct in6_addr *: STRFMT_STRUCT_IN6_ADDR_P,       \
        Q struct strfmt_end *: STRFMT_END_P,                 \
        default: (def))
  
  #define strfmt_typeof_p(X) \
    strfmt_typeof_p_g((X), const, \
        strfmt_typeof_p_g((X), , strfmt_badtype(X)))
  
  #define strfmt_typeof(X)                        \
    _Generic(strfmt_promote(X),             \
        int: STRFMT_INT,                    \
        unsigned: STRFMT_UINT,              \
        long: STRFMT_LONG,                  \
        unsigned long: STRFMT_ULONG,        \
        long long: STRFMT_LLONG,            \
        unsigned long long: STRFMT_ULLONG,  \
        float: STRFMT_FLOAT,                \
        double: STRFMT_DOUBLE,              \
        long double: STRFMT_LDOUBLE,        \
        default: strfmt_typeof_p(X))
  
  #define STRFMT_EXP1(X)      strfmt_typeof(X), STRFMT_STRINGIFY(X), (X)
  #define STRFMT_EXP2(X, ...) STRFMT_EXP1(X), STRFMT_EXP1(__VA_ARGS__)
  #define STRFMT_EXP3(X, ...) STRFMT_EXP1(X), STRFMT_EXP2(__VA_ARGS__)
  #define STRFMT_EXP4(X, ...) STRFMT_EXP1(X), STRFMT_EXP3(__VA_ARGS__)
  #define STRFMT_EXP5(X, ...) STRFMT_EXP1(X), STRFMT_EXP4(__VA_ARGS__)
  #define STRFMT_EXP6(X, ...) STRFMT_EXP1(X), STRFMT_EXP5(__VA_ARGS__)
  #define STRFMT_EXP7(X, ...) STRFMT_EXP1(X), STRFMT_EXP6(__VA_ARGS__)
  #define STRFMT_EXP8(X, ...) STRFMT_EXP1(X), STRFMT_EXP7(__VA_ARGS__)
  #define STRFMT_EXP9(X, ...) STRFMT_EXP1(X), STRFMT_EXP8(__VA_ARGS__)
  #define STRFMT_EXPAND(...) STRFMT_CALL(STRFMT_XPASTE(STRFMT_EXP, STRFMT_NARG(__VA_ARGS__)), __VA_ARGS__)
  
  #define strfmt_(dst, lim, error, fmt, ...) strfmt((dst), (lim), (error), (fmt), STRFMT_EXPAND(__VA_ARGS__))
  #define strfmt(...) strfmt_(__VA_ARGS__, STRFMT_END)
  
  size_t
  (strfmt)(void *dst, size_t lim, int *_error, const char *fmt, ...)
  {
    enum strfmt_type type;
    const char *exp;
    va_list ap;
    int error;
  
    *_error = 0;
    strlcpy(dst, "", lim);
  
    va_start(ap, fmt);
  
    do {
      type = va_arg(ap, enum strfmt_type);
      exp = va_arg(ap, char *);
  
      switch (type) {
      case STRFMT_...