Search code examples
cvariadic-functionsvariadic

Implementing optional argument of an variadic-using function?


I have a function:

log(const char *domain, int log_level, const char *fmt, ...)

I would like first and second arguments optional, so it's like following calls would be possible:

log("SYSTEM-A", 1, "Example %s", "...message");
log(1, "Example %s", "...message");
log("Example %s", "...message");

I've read about neat macro tricks, however they (almost?) all rely on trailing arguments to 'stand out' in a helper macro:

HELPER_SELECT(_1, _2, _3, func, ...) func

I however cannot use this method, because log() can take arbitrary number of variadic arguments. Is this possible to overcome somehow? With use of _Generics, maybe?


Solution

  • (1) log("SYSTEM-A", 1, "Example %s", "...message");
    (2) log(1, "Example %s", "...message");
    (3) log("Example %s", "...message");
    

    From what I understand:

    • (1) does not has % in it's first argument.
    • (2) first argument is int
    • (3) has % in it's argument.

    You can:

    • overload log macro on number of arguments
    • if one argument
      • choose (3)
    • else
    • _Generic on first argument
    • If first argument is an int
      • choose (2)
    • Else
      • call some _log_wrapper(const char *arg, ...)
        • inspect if strchr(arg, '%')
          • if it does, call va_list version of (3)
        • if it does not, call va_list version of (1)

    A possible implementation looks like this:

    #include <stdarg.h>
    #include <stdio.h>
    #include <string.h>
    
    void vlog_domain(const char *domain, int log_level, const char *fmt, va_list va)  {
        printf("domain\n");
    }
    void vlog_level(int log_level, const char *fmt, va_list va) {
        printf("level\n");
    }
    void vlog_normal(const char *fmt, va_list va) {
        printf("normal\n");
    }
    
    void _log_wrapper(int type, ...) {
        va_list va;
        va_start(va, type);
        if (type == 1) {
            int log_level = va_arg(va, int);
            const char *fmt = va_arg(va, const char *);
            vlog_level(log_level, fmt, va);
        } else {
            const char *arg = va_arg(va, const char*);
            if (!strchr(arg, '%')) {
                const char *domain = arg;
                int log_level = va_arg(va, int);
                const char *fmt = va_arg(va, const char*);
                vlog_domain(domain, log_level, fmt, va);
            } else {
                const char *fmt = arg;
                vlog_normal(fmt, va);
            }
        }
        va_end(va);
    }
    
    #define _log_1(_1)  vlog_normal(_1) // TODO
    #define _log_2(_1, ...)  _log_wrapper( \
            _Generic((_1), int: 1, char *: 2), _1, ##__VA_ARGS__)
    // this implementation supports max ca. 10 arguments
    #define _log_N(_9,_8,_7,_6,_5,_4,_3,_2,_1,_0,N,...)  _log_##N
    #define log(...)  _log_N(__VA_ARGS__,2,2,2,2,2,2,2,2,2,2,1)(__VA_ARGS__)
    
    int main() {
        log("SYSTEM-A", 1, "Example %s", "...message"); // domain
        log(1, "Example %s", "...message"); // level
        log("Example %s", "...message"); // normal
    }
    

    These are some time spent on writing the interface, that the next developer will most probably anyway not understand and will have to rewrite and refactor the whole code. I suggest instead to be as possible clear and write as possibly easy code to understand and just name your functions:

     logd("SYSTEM-A", 1, "Example %s", "...message");
     logl(1, "Example %s", "...message");
     log("Example %s", "...message");
    

    and be done with it.

    Inspect other projects how they solved logging with "domain+loglevel" (which sounds like syslog() severity and facility....) have a look how other projects solved logging interface. From my mind I enjoyed zephyr project solved logging, and it's open source so see inspect it's sources.