【问题标题】:Passing brace enclosed initializer list to variadic macro and expanding to std::pair<>将大括号括起来的初始化列表传递给可变参数宏并扩展为 std::pair<>
【发布时间】:2020-08-23 18:07:26
【问题描述】:

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

目前我正在使用一个单例,其中一个线程在旁边运行,并将日志消息和数据放入队列中(这样它们就不会阻塞事件)以供稍后处理。我在 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);

由于我的班级是单身人士,我使用宏来方便使用,它们都与:

#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 中收到以下错误。

有人曾经以这种方式使用过大括号括起来的初始化列表,或者知道如何修复这个错误吗?我对这种模式相当陌生。任何其他建议或改进这一点的指针表示赞赏。 (抱歉,帖子太长了)

【问题讨论】:

  • 根据我的经验(使用 Google 测试),宏和大括号初始化器表现不佳。
  • 错误的想法!在解析任何 c 或 c++ 代码之前处理宏。将某些东西从 c++ 传回宏是没有技巧的。顺便说一句:停止使用宏!我们在 2020 年,应该可以编写一些简单的函数,它可以根据需要采用任意数量的不同类型,
  • @Klaus 任何类型...但是大括号初始化列表没有类型,并且总是意味着为参数/参数对跳过模板参数推导,因此简单的可变参数模板不会好用,至少使用所需的语法。
  • @Klaus 有什么建议,需要调整吗?
  • @aschepler 在这种情况下你建议我做什么?坚持使用宏?任何其他模式建议?

标签: c++ macros initializer-list variadic


【解决方案1】:

注意声明

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

类型为std::pair&lt;const char*, std::string&gt; 的参数数量不可变。它实际上相当于添加了逗号的版本:

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

它有一个std::pair&lt;const char*, std::string&gt; 类型的参数,是一个C 风格的可变参数函数,第三个之后的参数只能通过&lt;cstdarg&gt;va_startva_argva_end 获得。这不可能是你想要的,因为这样的函数没有很好的方法知道有多少参数,而且花括号列表永远不可能是匹配 C 样式省略号的参数。

获得知道参数数量(和类型)的 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

【讨论】:

  • 这确实如我所愿,额外的 {} 确实不太好用,但比以前的使用好得多。谢谢。但我以前可以使用宏:GG_TRACE("some message") 或 GG_TRACE("some message", someLogData) 与 logData 或 {{},{}} 一起使用,现在是没有数据的简单才不是。编译器抛出调用是不明确的错误
  • 您不需要输入额外的{},因为GG_TRACE 会将它们添加进来。参见示例:coliru.stacked-crooked.com/a/90d6eee2954bdc45 不确定“模棱两可”的原因 - 还有其他重载?
  • 哇,太好了!现在我知道它在做什么了!但我仍在尝试解决那个模棱两可的电话问题.....现在我正在调查这个......stackoverflow.com/questions/3046889/…
  • 不知何故该宏对我不起作用....它抱怨缺少 } 或对重载的 push_to_queue 函数的模糊调用
  • 不知何故该宏对我不起作用....好吧,它在我添加初始化器列表时起作用,但只有一条消息的那个不起作用并引发“重载的 push_to_queue 不明确”错误
猜你喜欢
  • 2014-01-21
  • 2012-11-21
  • 1970-01-01
  • 2020-12-04
  • 1970-01-01
  • 2016-12-11
  • 1970-01-01
  • 1970-01-01
  • 2016-04-14
相关资源
最近更新 更多