You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++20编译时检测格式化字符串printf语法报错问题求助

编译时检查C++日志中的printf格式符

问题分析

你的核心需求是在C++上下文调用日志函数时,若使用%s/%i/%d这类printf风格格式符,直接触发编译错误。原代码存在几个关键问题:

  • 检查逻辑错误:当前是判断整个字符串是否等于某个printf格式符,而非字符串包含这些格式符
  • 检查时机错误:在格式化后的字符串上做检查,此时已经是运行时,无法触发编译错误
  • consteval构造函数的异常抛出不符合常量表达式要求
  • 头文件结构混乱(#pragma once位置错误、重复包含)

修正后的实现

以下是修复后的完整代码,核心思路是在原始格式字符串传入日志函数时就进行编译时检查,利用consteval和字符串查找确保非法格式符无法通过编译:

#include <source_location>
#include <string>
#include <string_view>
#include <fmt/format.h>
#include <iostream>
#include <algorithm>
#include <array>

#pragma once

using namespace std::string_view_literals;

// 需要拦截的printf风格格式符
constexpr auto printf_format_specifiers = std::array{ "%s"sv, "%i"sv, "%d"sv };

// 编译时字符串检查工具:确保输入字符串不包含任何printf格式符
struct CompileTimeFormatChecker {
    std::string_view fmt_str;

    consteval CompileTimeFormatChecker(std::string_view str) : fmt_str(str) {
        // 检查字符串中是否包含任何禁止的格式符
        for (const auto& spec : printf_format_specifiers) {
            if (str.find(spec) != std::string_view::npos) {
                // 抛出常量表达式异常,触发编译错误
                throw "C++日志禁止使用printf风格格式符,请使用{}类格式";
            }
        }
    }

    // 隐式转换为fmt::format_string,兼容原有的fmt库逻辑
    template <typename... Args>
    operator fmt::format_string<Args...>() const {
        return fmt::format_string<Args...>(fmt_str);
    }
};

#ifdef __cplusplus
// C++上下文:先经过编译时检查,再传给log函数
#define LOG_MESSAGE(fmt_str, ...) log(CompileTimeFormatChecker{fmt_str}, ##__VA_ARGS__)
#else
// C上下文:保留printf风格调用(仅作示例)
#define LOG_MESSAGE(strLogMessage, ...) logFromC(strLogMessage, __FILE__, __LINE__, ##__VA_ARGS__)
#endif

void _log(std::string_view formatted_msg, const std::source_location loc = std::source_location::current()) {
    std::cout << formatted_msg << " | 位置: " << loc.file_name() << ":" << loc.line() << std::endl;
}

template <typename... Args>
void log(CompileTimeFormatChecker fmt, Args&&... args) {
    std::string formatted_msg = fmt::format(static_cast<fmt::format_string<Args...>>(fmt), std::forward<Args>(args)...);
    _log(std::move(formatted_msg));
}

int main() {
    std::string foo = "foo";
    LOG_MESSAGE("foo {}", foo);  // 正常编译
    // LOG_MESSAGE("fail %s", foo); // 编译错误:触发禁止格式符检查
    // LOG_MESSAGE("fail %s");      // 编译错误:触发禁止格式符检查
}

关键修复点

  • 修正检查逻辑:遍历所有禁止的格式符,使用string_view::find检查字符串是否包含这些格式符,而非判断字符串是否完全匹配
  • 提前检查时机:通过CompileTimeFormatChecker包装原始格式字符串,在宏展开时就触发编译时检查,而非等到格式化完成后
  • 兼容fmt库:给CompileTimeFormatChecker添加到fmt::format_string的隐式转换,确保不破坏原有的fmt格式化逻辑
  • 规范头文件结构:将#pragma once放在文件最开头,避免重复包含问题
  • 合法的consteval异常:在consteval函数中抛出字符串字面量(常量表达式),确保编译时能正确触发错误

编译错误示例

当使用LOG_MESSAGE("fail %s", foo)时,编译器会输出类似错误:

error: expression '<throw-expression>' is not a constant expression

同时会显示自定义的错误提示信息,明确告知开发者需要使用fmt风格的格式符。

内容的提问来源于stack exchange,提问作者user8694474

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 19:10:31