【问题标题】:Variadic Macro: cannot pass objects of non-trivially-copyable type through '...'可变参数宏:不能通过“...”传递非平凡可复制类型的对象
【发布时间】:2018-08-15 09:44:49
【问题描述】:

我正在尝试为日志记录机制编写一个宏。我写了一个可变参数宏,但它不适用于std::string。代码如下所示:

#include <stdio.h>
#include <string>


#define LOG_NOTE(m, ...) printf(m, ##__VA_ARGS__)

int main()
{
    std::string foo = "random string";
    int bar = 5;
    LOG_NOTE("%s %d %s", "Hello World", bar, foo);

    return 0;
}

如果我像下面这样调用宏,我不会收到任何错误。

LOG_NOTE("%s %d %s", "Hello World", bar, "random string");

编译器输出:

在函数“int main()”中:5:49:错误:无法传递对象 非平凡可复制类型 'std::string {aka 类 std::basic_string}' 到 '...' 11:5: 注意:在扩展 宏“LOG_NOTE”

【问题讨论】:

  • 顺便说一句,如果你使用 gcc 它的 -E 标志在预处理器之后获取输出,在编写宏时有很大帮助。
  • 您需要格式,还是只是空格分隔的值?
  • 我实际上只需要空格分隔的值,目前我正在尝试根据答案实现它,但出现链接器错误

标签: c++ macros variadic-macros


【解决方案1】:

我写了一个可变参数宏

不要。使用可变参数模板函数。

您遇到的实际问题是您试图通过 C API (printf) 传递 C++ 对象 (std::string)。这是不可能的。

你需要一些转换机制,例如:

#include <stdio.h>
#include <string>

template<class T>
decltype(auto) convert_for_log_note(T const& x)
{
    return x;
}

decltype(auto) convert_for_log_note(std::string const& x)
{
    return x.c_str();
}


template<class...Args> 
void LOG_NOTE(const char* format, Args&&...args)
{
    printf(format, convert_for_log_note(args)...);
}

int main()
{
    std::string foo = "random string";
    int bar = 5;
    LOG_NOTE("%s %d %s\n", "Hello World", bar, foo);

    return 0;
}

示例输出:

Hello World 5 random string

http://coliru.stacked-crooked.com/a/beb3431114833860

更新:

对于 C++11,您需要手动拼出返回类型:

#include <stdio.h>
#include <string>

template<class T>
T const& convert_for_log_note(T const& x)
{
    return x;
}

const char* convert_for_log_note(std::string const& x)
{
    return x.c_str();
}


template<class...Args> 
void LOG_NOTE(const char* format, Args&&...args)
{
    printf(format, convert_for_log_note(args)...);
}

int main()
{
    std::string foo = "random string";
    int bar = 5;
    LOG_NOTE("%s %d %s\n", "Hello World", bar, foo);

    return 0;
}

【讨论】:

  • 平心而论,记录宏是众所周知的“无宏”规则的豁免,原因有三个:__FILE____LINE__ 以及将它们变成无操作语句的能力在发布版本中。您无法消除函数调用参数的副作用。
  • @MSalters 只要 c++ 委员会表现良好,我们很快就会拥有 std::source_location,它与 if constexpr 一起将完全消除对宏的需求。请注意,只要您通过通用引用传递参数,参数副作用就不是问题。显然通过 LOG("%s%",mything.to_string().c_str());确实会引起副作用,显然应该避免。最好为 convert_for_log_note(MyThing const&amp;) 类型提供 ADL 重载。
  • 这正是我正在寻找的,但它似乎在 C++11 中不起作用,至少在使用 g++ -std=c++11 的 GNU GCC v7.1.1 中不起作用。但是它在 C++14 中确实有效。任何人都知道如何修改它以使其在 C++11 中工作?
  • @Oliver 我已经添加了一个可以为 c++11 编译的更新。 godbolt.org/z/M5GmyD
  • 这太酷了。我遇到的一件事是,使用decltype(auto) 作为std::string 特化convert_for_log_note 模板的返回类型的能力取决于它在代码中的首次使用方式。 (类似于stackoverflow.com/a/43514762/430067.)我只是将其更改为const char*,因为我没有看到通用返回类型的任何原因。
【解决方案2】:

这里的问题不是可变参数宏,而是对printf 的调用。看看documentation:格式说明符"%s" 对应于char*,而不是std::stringprintf 只能处理原始的内置类型。您可以将调用更改为

LOG_NOTE("%s %d %s", "Hello World", bar, foo.c_str());

解决这个问题。

【讨论】:

  • 或者,使用 Boost 格式。它确实支持std::string 和许多其他用户定义的类型。
【解决方案3】:

你不能将对象传递给printf,所以你现在必须使用

LOG_NOTE("%s %d %s", "Hello World", bar, foo.c_str());

如果您不需要格式化,只需将每个参数用空格分隔,您可以简单地使用可变参数模板而不是 MACRO:

template <typename ... Ts>
void LOG_NOTE(const Ts&...args)
{
    const char* sep = "";
    (((std::cout << sep << args), sep = " "), ...); // C++17 folding expression
    // C++11/C++14 version are more verbose:
    // int dummy[] = {0, ((std::cout << sep << args), (sep = " "), 0)...};
    // static_cast<void>(dummy); // avoid warning for unused variable
}

int main()
{
    std::string foo = "random string";
    int bar = 5;
    LOG_NOTE("Hello World", bar, foo);
}

Demo

【讨论】:

  • 感谢您的输入@Jarod42,但我已经找到了 C++17 的类似答案。但是,我正在寻找头文件和源文件分开的 C++11 示例。
  • template 不适合在头文件和 cpp 文件之间进行拆分。
  • 添加了 C++11 版本。
【解决方案4】:

我无法让@richardhodges 在我尝试过的任何 C++11 编译器中工作的好解决方案。但是,以下内容适用于gcc -std=c++11

#include <stdio.h>
#include <string>

template<class T>
T convert_for_log_note(T const& x)
{
    return x;
}

inline const char* convert_for_log_note(std::string const& x)
{
    return x.c_str();
}


template<class...Args> 
void LOG_NOTE(const char* format, Args&&...args)
{
    printf(format, convert_for_log_note(args)...);
}

int main()
{
    std::string foo = "random string";
    int bar = 5;
    LOG_NOTE("%s %d %s\n", "Hello World", bar, foo);

    return 0;
}

inline 关键字对于 Arduino C++ 编译器的上述解决方案是必需的,而其他 g++ 编译器不需要它(反正我尝试过的那些)。如果没有这个关键字,Arduino 代码会编译,但链接器会抱怨多个定义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-17
    • 1970-01-01
    • 2014-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    相关资源
    最近更新 更多