使用 std::ostream 打印可变参数包的最简单方法是啥?
Posted
技术标签:
【中文标题】使用 std::ostream 打印可变参数包的最简单方法是啥?【英文标题】:What is the easiest way to print a variadic parameter pack using std::ostream?使用 std::ostream 打印可变参数包的最简单方法是什么? 【发布时间】:2015-02-07 02:49:22 【问题描述】:使用std::ostream
打印参数包的最简单方法是什么,以逗号分隔?
例子:
template<typename... Args>
void doPrint(std::ostream& out, Args... args)
out << args...; // WRONG! What to write here?
// Usage:
int main()
doPrint(std::cout,34,"bla",15); // Should print: 34,bla,15
注意:
可以假设 <<
运算符的相应重载可用于所有类型的参数包。
【问题讨论】:
在 C++17 中,你会说(out << ... <<< args);
。
注意:这不是重复的——foo << X << Y;
通常与foo << X; foo << Y;
不同,尤其是当foo
具有诸如打开磁盘上的文件等副作用时。
@MSalters,foo << X << Y
也没有指定 X
和 Y
的评估顺序(即在 C++17 之前)
【参考方案1】:
没有你想要的递归调用和逗号。
在c++11/c++14通过参数包扩展:
template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
out << std::forward<Arg>(arg);
using expander = int[];
(void)expander0, (void(out << ',' << std::forward<Args>(args)), 0)...;
DEMO
在c++17 中使用折叠表达式:
template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
out << std::forward<Arg>(arg);
((out << ',' << std::forward<Args>(args)), ...);
DEMO 2
【讨论】:
这个是最简单的。只是看起来怪怪的,呵呵。 @GermánDiago en.cppreference.com/w/cpp/language/parameter_pack 给出了一个类似的例子:int dummy[sizeof...(Ts)] = (std::cout << args, 0)... ;
附带说明,您可以跳过using
声明,直接写(void)(int[])(std::cout << ',' << std::forward<Args>(args)), 0)...;
。您也不需要大括号中的第一个零或内部 void
演员表。
@AlexanderRevo inserting 到ostream
意味着将参数传递给operator<<
的某个重载。你永远不知道操作员期望什么参数类型
我的 C++ 一定已经过时了。我根本无法解析。【参考方案2】:
在 C++17 中,将有一种更简单的方法(正如 Kerrek SB 在 cmets 中所暗示的那样;这实际上出现在 N4606,第一个后 C++14 草案中),称为 fold expressions:
代码是:
(out << ... << args);
而模式expression op
...
op parameter-pack
称为二元左折叠,其定义等价于@987654326 @ expression
op arg1) op arg2) op arg3)
.... op argN
.
我认为对于这样的表达式语句来说,外括号并不是绝对必要的,但如果折叠表达式是另一个运算符的操作数,那么它们要么是必需的,要么是一个非常好的主意:)
【讨论】:
使用分隔符:((out << ", " << args), ...);
【参考方案3】:
通常的答案是定义两个单独的重载,一个空的作为基本情况:
// base case
void doPrint(std::ostream& out)
template <typename T, typename... Args>
void doPrint(std::ostream& out, T t, Args... args)
out << t; // add comma here, see below
doPrint(out, args...);
当然,在实际代码中,我不会每次都复制参数,而是使用转发引用,但你明白了。
如果您想在每一项之后添加逗号,即使是在最后一项之后,只需将 out << t
替换为 out << t << ','
。
如果您只想在内部使用逗号,而不是在最后一个元素之后,则需要一个单独的不打印逗号的单参数重载,并且通用重载在包之前采用两个不同的参数,即:
template <typename T>
void doPrint(std::ostream& out, T t)
out << t;
template <typename T, typename U, typename... Args>
void doPrint(std::ostream& out, T t, U u, Args... args)
out << t << ',';
doPrint(out, u, args...);
【讨论】:
你能详细说明一下转发参考吗?我对 C++11 的东西还不是很坚定,但是复制参数似乎有点过头了。 @gexicide:这个网站上有数千个重复项,只需四处搜索。您正在寻找Args &&... args
和std::forward<Args>(args)...
。【参考方案4】:
参数包扩展仅适用于普通函数调用,不适用于中缀运算符。因此,您需要将s << x
语法转换为普通函数调用语法f(s, x)
:
template<class Head>
void print_args_(std::ostream& s, Head&& head)
s << std::forward<Head>(head);
template<class Head, class... Tail>
void print_args_(std::ostream& s, Head&& head, Tail&&... tail)
s << std::forward<Head>(head);
print_args_(s, std::forward<Tail>(tail)...);
template<class... Args>
void print_args(Args&&... args)
print_args_(std::cout, std::forward<Args>(args)...);
【讨论】:
【参考方案5】:我知道这是一个老问题,但它对我的问题帮助很大。我根据这篇帖子的答案创建了一个实用程序类,我想分享我的结果。
考虑到我们使用 C++11 或更高版本的 C++,这个类提供了 print 和 println 函数来在调用标准输出流之前组合字符串,避免并发问题。这些是可变参数函数,它们使用模板来打印不同的数据类型。
您可以在我的 github 上查看它在生产者-消费者问题中的用途:https://github.com/eloiluiz/threadsBar
所以,这是我的代码:
class Console
private:
Console() = default;
inline static void innerPrint(std::ostream &stream)
template<typename Head, typename... Tail>
inline static void innerPrint(std::ostream &stream, Head const head, Tail const ...tail)
stream << head;
innerPrint(stream, tail...);
public:
template<typename Head, typename... Tail>
inline static void print(Head const head, Tail const ...tail)
// Create a stream buffer
std::stringbuf buffer;
std::ostream stream(&buffer);
// Feed input parameters to the stream object
innerPrint(stream, head, tail...);
// Print into console and flush
std::cout << buffer.str();
template<typename Head, typename... Tail>
inline static void println(Head const head, Tail const ...tail)
print(head, tail..., "\n");
;
比起重载<<
运算符或使用复杂的流函数,我更喜欢这种替代方法。它是一种递归方法,但并不难理解。
【讨论】:
我会更改= delete
中的构造函数。由于只有静态方法,实例化Console
对象是没有意义的【参考方案6】:
同样适用于std::wostream
的通用形式:
template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out)
return out;
template <typename CharT, typename Traits, typename T>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t)
return (out << std::forward<T>(t));
template <typename CharT, typename Traits, typename T, typename... Args>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t, Args &&...args)
return Print( Print(out, std::forward<T>(t)), std::forward<Args>(args)... );
在常见情况下,我无法使其与 std::endl
一起使用(在特定情况下可以处理 std::endl
,例如当它是第一个或最后一个参数时,但不是在常见情况下,尤其是如果单个呼叫中有多个std::endl
)。如果您确实需要 std::endl
,您仍然可以使用 '\n'
或使用 std::endl
指定模板参数:
Print(std::cout, "hello world", std::endl<char, std::char_traits<char>>);
std::endl
和 '\n'
之间的 differences
'\n'
不会转换为编译代码所针对的平台的行尾格式(但在文本模式下仍会转换)。
'\n'
不会用std::flush
刷新流(但如果程序在终端上运行,它仍然是flushes std::cout
)
所以对我来说可以使用'\n'
,甚至可以使用preferred。
仍然可以使用其他一些 IO 操纵器:
Print(std::cout, std::hex, 11, '\n');
我还实现了sprintf
对应物,它与可变参数模板一起使用并返回std::string
:
template <typename CharT = char, typename Traits = std::char_traits<CharT>, typename... Args>
std::basic_string<CharT, Traits>
SPrint(Args &&...args)
std::basic_stringstream<CharT, Traits> ss;
Print(ss, std::forward<Args>(args)...);
return std::move(ss.str());
这里有一些demos。
【讨论】:
以上是关于使用 std::ostream 打印可变参数包的最简单方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
‘operator<<’ 不匹配(操作数类型是 ‘std::ostream’ aka ‘std::basic_ostream<char>’ 和 ‘const std::type
错误:'operator<<' 不匹配(操作数类型为'std::ostream' aka'std::basic_ostream<char>' 和'std::_List_iter
错误:无法将‘std::basic_ostream<char>’左值绑定到‘std::basic_ostream<char>&&’