将大括号括起来的初始化列表传递给可变宏并扩展到 std::pair<>

Passing brace enclosed initializer list to variadic macro and expanding to std::pair<>

我目前正在开发线程日志记录库,这是一个更大的图形绘制项目的第一部分(用于个人学习和技能发展)。

目前我正在使用一个带有线程 运行 的单例,并将日志消息和数据放入队列(这样它们就不会阻塞事件)以便稍后处理。我围绕 std::map<> 编写了一个小包装器作为 LogData,记录器可以通过以下方式在流或文件中显示它。

[0.000155][DEBUG]: FILE LOGGER ADDED { ID="1" LVL="TRACE" }

构造函数允许传递 string、char*、floats、int、short long 等,并将其转换为字符串,稍后显示在这些括号中。

目前构建这个LogData有点臃肿。生成上述日志的示例:

GG::LogData id_dat;
id_dat.push("ID", id);
id_dat.push("LVL",GG::loglevel_toString(lvl));
GG_DEBUG("FILE LOGGER ADDED", id_dat);

因为我的 Class 是单例,所以我使用宏来方便使用,它们都和 :

一样
#define GG_TRACE(MESS, ...) GG::Logging::get()->push_to_queue(GG::LOG_LEVEL::TRACE, MESS, ##__VA_ARGS__);

这适用于大多数情况。但我想让它可以在一条线上使用,并让它不那么臃肿。我想要达到的效果是这样的:

//Desired Usage
GG_TRACE("VARIADIC TEST", {"X","1"}, {"Y","2"}, {"Z","3"});

此处展开:

void Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess, std::pair<const char*, std::string> log_data ...)

我会使用大括号初始化列表来生成日志数据,然后我会遍历可变参数并在函数中构造 LogData,而不必每次都手动执行。

我让它直接使用这样的函数。

void test(std::pair<char*,int> p) {
    GG::LogData dat;
    dat.push("key", p.first);
    dat.push("value", p.second);
    GG_TRACE("PAIR: ", dat);
}
// In main...
test({ "test",1 });

而且效果很好。但是当我尝试使用相同的模式并将宏转发到 push_to_queue 函数时,我在 GCC 中遇到以下错误。

有人曾经以这种方式使用大括号括起来的初始化列表或知道如何修复这个错误吗?我对这种模式还很陌生。任何其他对此进行改进的建议或指示表示赞赏。 (抱歉长post)

注意声明

void Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess,
    std::pair<const char*, std::string> log_data ...);

类型为 std::pair<const char*, std::string> 的参数数量不可变。它实际上等同于添加逗号的版本:

void Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess,
    std::pair<const char*, std::string> log_data,
    ...);

它有一个 std::pair<const char*, std::string> 类型的参数,并且是一个 C-style 可变参数函数,第三个参数之后的参数只能通过 <cstdarg>va_startva_arg,以及 va_end。这不是你想要的,因为没有好的方法让这样的函数知道有多少参数,而且花括号列表永远不可能是匹配 C-style 省略号的参数。

获得知道参数数量(和类型)的 C++ 样式可变参数函数的唯一方法是作为具有可变参数模板参数的模板。但是作为参数的花括号列表意味着没有模板参数推导,所以要让它好用会很棘手。

但我们可以使用 std::initializer_list 并在宏中添加更多 {} 来使此语法正常工作:

#include <initializer_list>
#include <utility>
#include <string>

void GG::Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess,
    std::initializer_list<std::pair<const char*, std::string>> log_data)
{
    GG::LogData dat;
    for (const auto &kv : log_data)
        dat.push(kv.first, kv.second);
    // Do the rest...
}

#define GG_TRACE(MESS, ...) (GG::Logging::get()->push_to_queue( \
     GG::LOG_LEVEL::TRACE, MESS, {__VA_ARGS__}))

所以扩展会有一个像 {{"X","1"}, {"Y","2"}, {"Z","3"}} 这样的参数,其中外部 {} 用于 std::initializer_list,内部 {} 用于每个 std::pair .