实现可变参数使用函数的可选参数?
Implementing optional argument of an variadic-using function?
我有一个功能:
log(const char *domain, int log_level, const char *fmt, ...)
我希望第一个和第二个参数是可选的,因此可以进行以下调用:
log("SYSTEM-A", 1, "Example %s", "...message");
log(1, "Example %s", "...message");
log("Example %s", "...message");
我读过一些简洁的宏技巧,但是它们(几乎?)都依赖于辅助宏中 'stand out' 的尾随参数:
HELPER_SELECT(_1, _2, _3, func, ...) func
但是我不能使用这个方法,因为 log()
可以接受任意数量的可变参数。这有可能以某种方式克服吗?使用 _Generics
,也许?
这在 C 中实际上是不可能的 - 可变参数函数一次解决了这个问题,但你试图解决它两次。
考虑使用简单的选项结构。让所有选项 空值 等同于 0
(或具有默认初始化程序),以便调用者不需要记住任何标记值。
struct loggeropts {
const char *domain;
int level;
};
void logger(struct loggeropts *opts, const char *fmt, ...) {
if (opts) {
if (opts->domain) { /* */ }
if (opts->level) { /* */ }
}
/* impl */
}
int main(void) {
struct loggeropts opts = { .domain = "SYSTEM-A" };
logger(&opts, "#Example %u %s", 42, "information");
logger(NULL, "#Example %u %s", 44, "different information");
}
如果不经常使用可选参数,您可以将调用隐藏在宏后面。
#define logger(string, ...) logger_with_opts(NULL, string, __VA_ARGS__)
void logger_with_opts(struct loggeropts *opts, const char *fmt, ...);
或者,在格式字符串中有一个特殊部分来标识传递的选项,并确保它们在通常的可变参数之前传递。记住在向前传递之前移动你的 fmt
指针。不过,这看起来确实很脆弱,并且有额外的开销。
logger("{{@d@l}}Example %s", "SYSTEM-A", 1, "...message");
logger("{{@d}}Example %s", "SYSTEM-B", "...message");
除了通用功能之外,我很可能会建议仅具有特定于域的日志记录功能。
(1) log("SYSTEM-A", 1, "Example %s", "...message");
(2) log(1, "Example %s", "...message");
(3) log("Example %s", "...message");
据我了解:
- (1) 的第一个参数中没有
%
。
- (2) 第一个参数是 int
- (3) 的参数中有
%
。
您可以:
- 根据参数数量重载
log
宏
- 如果一个参数
- 选择 (3)
- 其他
- _第一个参数是通用的
- 如果第一个参数是一个整数
- 选择 (2)
- 其他
- 打电话给一些
_log_wrapper(const char *arg, ...)
- 检查是否
strchr(arg, '%')
- 如果是,请调用 (3) 的 va_list 版本
- 如果没有,调用 (1)va_list 版本
可能的实现如下所示:
#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
}
这些是花在写界面上的一些时间,下一个开发者很可能无论如何都看不懂,将不得不重写和重构整个代码。相反,我建议尽可能清楚并编写尽可能容易理解的代码,并仅命名您的函数:
logd("SYSTEM-A", 1, "Example %s", "...message");
logl(1, "Example %s", "...message");
log("Example %s", "...message");
并完成它。
检查其他项目如何使用“domain+loglevel”(这听起来像 syslog()
severity and facility....)解决日志记录问题,看看其他项目如何解决日志记录接口问题。在我看来,我喜欢 zephyr project solved logging,而且它是开源的,所以请查看它的来源。
我有一个功能:
log(const char *domain, int log_level, const char *fmt, ...)
我希望第一个和第二个参数是可选的,因此可以进行以下调用:
log("SYSTEM-A", 1, "Example %s", "...message");
log(1, "Example %s", "...message");
log("Example %s", "...message");
我读过一些简洁的宏技巧,但是它们(几乎?)都依赖于辅助宏中 'stand out' 的尾随参数:
HELPER_SELECT(_1, _2, _3, func, ...) func
但是我不能使用这个方法,因为 log()
可以接受任意数量的可变参数。这有可能以某种方式克服吗?使用 _Generics
,也许?
这在 C 中实际上是不可能的 - 可变参数函数一次解决了这个问题,但你试图解决它两次。
考虑使用简单的选项结构。让所有选项 空值 等同于 0
(或具有默认初始化程序),以便调用者不需要记住任何标记值。
struct loggeropts {
const char *domain;
int level;
};
void logger(struct loggeropts *opts, const char *fmt, ...) {
if (opts) {
if (opts->domain) { /* */ }
if (opts->level) { /* */ }
}
/* impl */
}
int main(void) {
struct loggeropts opts = { .domain = "SYSTEM-A" };
logger(&opts, "#Example %u %s", 42, "information");
logger(NULL, "#Example %u %s", 44, "different information");
}
如果不经常使用可选参数,您可以将调用隐藏在宏后面。
#define logger(string, ...) logger_with_opts(NULL, string, __VA_ARGS__)
void logger_with_opts(struct loggeropts *opts, const char *fmt, ...);
或者,在格式字符串中有一个特殊部分来标识传递的选项,并确保它们在通常的可变参数之前传递。记住在向前传递之前移动你的 fmt
指针。不过,这看起来确实很脆弱,并且有额外的开销。
logger("{{@d@l}}Example %s", "SYSTEM-A", 1, "...message");
logger("{{@d}}Example %s", "SYSTEM-B", "...message");
除了通用功能之外,我很可能会建议仅具有特定于域的日志记录功能。
(1) log("SYSTEM-A", 1, "Example %s", "...message"); (2) log(1, "Example %s", "...message"); (3) log("Example %s", "...message");
据我了解:
- (1) 的第一个参数中没有
%
。 - (2) 第一个参数是 int
- (3) 的参数中有
%
。
您可以:
- 根据参数数量重载
log
宏 - 如果一个参数
- 选择 (3)
- 其他
- _第一个参数是通用的
- 如果第一个参数是一个整数
- 选择 (2)
- 其他
- 打电话给一些
_log_wrapper(const char *arg, ...)
- 检查是否
strchr(arg, '%')
- 如果是,请调用 (3) 的 va_list 版本
- 如果没有,调用 (1)va_list 版本
- 检查是否
- 打电话给一些
可能的实现如下所示:
#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
}
这些是花在写界面上的一些时间,下一个开发者很可能无论如何都看不懂,将不得不重写和重构整个代码。相反,我建议尽可能清楚并编写尽可能容易理解的代码,并仅命名您的函数:
logd("SYSTEM-A", 1, "Example %s", "...message");
logl(1, "Example %s", "...message");
log("Example %s", "...message");
并完成它。
检查其他项目如何使用“domain+loglevel”(这听起来像 syslog()
severity and facility....)解决日志记录问题,看看其他项目如何解决日志记录接口问题。在我看来,我喜欢 zephyr project solved logging,而且它是开源的,所以请查看它的来源。