带默认参数的可变参数模板实参推导 日志类编译问题求解
问题根因
代码编译失败核心是类模板参数推导(CTAD)的触发规则,和类模板部分显式指定参数时的实例化逻辑:
- 初始无日志级别版本能正常工作,是因为写
log inst("a","b","c")时没有显式指定任何类模板参数,编译器会通过你定义的CTAD推导指引,根据传入的3个const char*实参,将类模板的str...参数包推导为3个const char*类型,此时构造函数的可变参数包长度为3,刚好匹配传入的3个日志内容参数,后面的file/func/line自动使用默认值,逻辑正常。 - 加了日志级别后写
log<INFO> inst(...)时,已经显式指定了第一个模板参数level=INFO,此时CTAD完全不会触发,编译器会直接使用显式指定的模板参数实例化类:你只给了第一个模板参数,剩下的str...参数包会被推导为空包,对应构造函数的可变参数包长度为0。这时候传入的3个日志字符串,会按顺序匹配构造函数的形参:第一个填file、第二个填func、第三个要填size_t类型的line,自然就出现了const char*无法转size_t的编译错误。
可行修改方案
最贴合你原有调用习惯(支持log<级别>(日志内容...)写法)的方案是放弃直接构造log类实例,改用模板化的函数调用算子实现,避开类模板部分指定参数时参数包为空的问题:
#include <iostream> enum logging_level { INFO, WARN, ERROR }; // 日志核心逻辑模板函子 template<logging_level level> struct log_impl { template<typename... Args> void operator()( Args&&... args, const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION(), const size_t line = __builtin_LINE() ) const { // 打印日志头:级别、文件、函数、行号 std::cout << "["; switch(level) { case INFO: std::cout << "INFO"; break; case WARN: std::cout << "WARN"; break; case ERROR: std::cout << "ERROR"; break; } std::cout << "] [" << file << "] [" << func << "] [" << line << "] "; // 折叠表达式打印可变日志内容 ((std::cout << args << " "), ...); std::cout << std::endl; } }; // 支持<级别>模板参数调用的日志入口 template<logging_level level> constexpr log_impl<level> log{}; int main() { // 完全符合预期的调用方式 log<INFO>("THIS WORKS", "ASD", "ASD"); log<WARN>("warning message", 123, 45.6); return 0; }
方案说明
- 实现中
log<INFO>是一个log_impl<INFO>类型的编译期常量,调用时会执行operator(),此时Args...模板参数会根据传入的日志内容自动推导,不会出现空包问题。 - 可变参数包后跟随带默认值的
file/func/line参数逻辑正常,编译器会优先用传入的实参匹配参数包,剩余位置自动填充默认的源码位置信息,不会出现参数错位。 - 如果不需要保留
log<INFO>的尖括号调用形式,也可以直接把日志级别作为第一个函数入参,用普通函数模板实现,逻辑更简单:
template<typename... Args> void log( logging_level level, Args&&... args, const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION(), const size_t line = __builtin_LINE() ) { // 打印逻辑和上述实现一致 } // 调用方式 log(INFO, "test log", 123);
内容的提问来源于stack exchange,提问作者Jaan
相关产品推荐
相关产品推荐

