利用可变参数模板与元组实现带默认参数的编译期日志定义
实现带编译期默认参数的日志定义
要让未传入的参数使用预设默认值且所有处理在编译期完成,我们可以通过编译期元组补全和constexpr工具函数来实现,以下是修改后的完整代码及说明:
修改后的代码
#include <iostream> #include <tuple> #include <memory> #include <type_traits> #include <utility> class MyLogger { public: template<typename... Args> void Log(const char* format, Args... args) { printf(format, args...); printf("\n"); } }; static inline std::unique_ptr<MyLogger> logger = std::make_unique<MyLogger>(); // 编译期获取类型的预设默认值 template<typename T> constexpr T default_value() { if constexpr (std::is_same_v<T, int>) { return 0; } else if constexpr (std::is_same_v<T, float>) { return 0.0f; } // 可扩展其他类型的默认值,比如bool返回false,std::string返回空串等 else { return T{}; // 基础类型的零初始化 } } template<typename FMT, typename... RequiredArgs> struct LogLineDef { // 支持传入部分参数,自动补全默认值 template<typename... ProvidedArgs> constexpr LogLineDef(ProvidedArgs... args) : values(complete_tuple(std::make_tuple(std::forward<ProvidedArgs>(args)...))) {} std::tuple<RequiredArgs...> values; private: // 编译期补全元组的核心实现 template<typename Tuple, size_t... ProvIdx, size_t... RemIdx> static constexpr std::tuple<RequiredArgs...> complete_tuple_impl(Tuple&& prov_tuple, std::index_sequence<ProvIdx...>, std::index_sequence<RemIdx...>) { return std::make_tuple( // 取传入的参数 std::get<ProvIdx>(std::forward<Tuple>(prov_tuple))..., // 补全剩余参数的默认值 default_value<std::tuple_element_t<sizeof...(ProvIdx) + RemIdx, std::tuple<RequiredArgs...>>>()... ); } template<typename... ProvidedArgs> static constexpr std::tuple<RequiredArgs...> complete_tuple(std::tuple<ProvidedArgs...> provided) { // 静态断言:传入参数不能超过要求的数量 static_assert(sizeof...(ProvidedArgs) <= sizeof...(RequiredArgs), "Number of provided arguments exceeds required count"); using ProvidedSeq = std::make_index_sequence<sizeof...(ProvidedArgs)>; using RemainingCount = std::make_index_sequence<sizeof...(RequiredArgs) - sizeof...(ProvidedArgs)>; return complete_tuple_impl(std::move(provided), ProvidedSeq{}, RemainingCount{}); } }; constexpr char FMT1[] = "A:%d, B:%d, C:%d, D:%f "; using L1 = LogLineDef<std::integral_constant<const char*, FMT1>, int, int, int, float>; template<typename FMT, typename... Args> void log_helper(const char* message, const LogLineDef<FMT, Args...>& logger_line_def) { std::apply([message](const auto&... args) { logger->Log((std::string(FMT::value) + message).c_str(), args...); }, logger_line_def.values); } #define LOG(logger_line_def, message) log_helper(message, logger_line_def) int main() { LOG(L1(1, 2, 3, 4.f), "error"); // 全参数传入,正常输出 LOG(L1(1, 2, 3), "error"); // 自动补全D的默认值0.0f LOG(L1(1, 2), "error"); // 自动补全C的默认值0,D的默认值0.0f }
关键实现说明
编译期默认值生成:
default_value函数通过if constexpr在编译期为不同类型返回预设默认值,完全避免运行期计算。元组补全逻辑:
- 构造函数接收任意数量的前N个参数,转为临时元组
- 通过
std::index_sequence编译期计算剩余参数数量,自动补全对应类型的默认值,生成符合RequiredArgs要求的完整元组 - 静态断言防止传入过多参数,提前在编译期暴露错误
编译期保证:
所有元组操作、默认值计算均为constexpr,编译器会在编译阶段完成所有补全逻辑,运行期直接使用预先生成的完整参数元组,无额外性能开销。
输出结果
运行上述代码会输出:
A:1, B:2, C:3, D:4.000000 error A:1, B:2, C:3, D:0.000000 error A:1, B:2, C:0, D:0.000000 error
内容的提问来源于stack exchange,提问作者Enes Aygün
相关产品推荐
相关产品推荐

