使用std :: ostream打印可变参数包的最简单方法是什么?

时间:2014-12-09 09:11:43

标签: c++ templates c++11 variadic-templates

使用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
}

注意: 可以假设<<运算符的相应重载可用于所有类型的参数包。

6 个答案:

答案 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)...};
}

DEMO


在使用折叠表达式的中:

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

答案 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