使用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
}
注意:
可以假设<<
运算符的相应重载可用于所有类型的参数包。
答案 0 :(得分:35)
没有你想要的递归调用和逗号。
template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
out << std::forward<Arg>(arg);
using expander = int[];
(void)expander{0, (void(out << ',' << std::forward<Args>(args)), 0)...};
}
在使用折叠表达式的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)), ...);
}
答案 1 :(得分:14)
在C ++ 17中,将有一种更简单的方法(正如Kerrek SB在评论中所暗示的那样;这实际上存在于N4606,第一个C ++ 14后草案中),称为fold expressions:
代码如下:
(out << ... << args);
并且模式 expression op
...
op parameter-pack
被称为二进制左折叠,其定义相当于(((
expression
op arg1) op arg2) op arg3)
.... op argN
。
我认为对于像这样的表达式语句,外括号并不是绝对必要的,但如果fold表达式是另一个运算符的操作数,则它们是必需的,或者是一个非常好的主意:)
答案 2 :(得分:8)
通常的答案是定义两个单独的重载,对于基本情况使用空的重载:
// 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...);
}
答案 3 :(得分:6)
参数包扩展仅适用于普通函数调用,而不适用于中缀运算符。因此,您需要将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)...);
}
答案 4 :(得分:1)
我知道这是一个老问题,但它对我的问题帮助很大。我根据这篇帖子答案创建了一个实用工具类,我想分享我的结果。
考虑到我们使用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");
}
};
我比重载<<
运算符或使用复杂的流函数更喜欢这种替代方法。它是一种递归方法,但并不难理解。
答案 5 :(得分:1)
也可以与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
的情况下)。您仍然可以改用'\n'
或在确实需要std::endl
的情况下使用带有指定模板参数的std::endl
:
Print(std::cout, "hello world", std::endl<char, std::char_traits<char>>);
std::endl
和'\n'
之间的differences
'\n'
不会转换为编译所针对的平台的行尾格式(但在文本模式下仍会转换)。'\n'
不会用std::flush
刷新流(但如果程序在终端上运行,它仍会std::cout
flushes)所以对我来说,可以使用'\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。