Variadic 宏:不能通过“...”传递非平凡可复制类型的对象

Variadic Macro: cannot pass objects of non-trivially-copyable type through '...'

我正在尝试为日志记录机制编写一个宏。我写了一个可变参数宏,但它不适用于 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");

编译器输出:

In function 'int main()': 5:49: error: cannot pass objects of non-trivially-copyable type 'std::string {aka class std::basic_string}' through '...' 11:5: note: in expansion of macro 'LOG_NOTE'

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

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

解决这个问题。

I wrote a variadic macro

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

您遇到的实际问题是您试图通过 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,您需要手动拼出 return 类型:

#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;
}

您不能将对象传递给 printf,因此您当前必须使用

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

如果你不需要格式化,只是写每个参数用 space 分隔,你可以简单地使用可变参数模板而不是 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

我无法获得@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 代码会编译,但链接器会抱怨多个定义。