使用C++的可变参数模板迭代参数包。

6

作为一个日志库的一部分,我想要能够迭代参数包,将每个值写入流中。然而,我的第一次尝试并不能编译通过。第一个错误是“error C2144: syntax error : 'int' should be preceded by '}'”。

#include <sstream>
#include <ostream>
#include <iomanip>
#include <fstream>

template <typename ...Args>
std::ostream & Write(std::ostream & o, std::initializer_list<Args...> list) 
{
    size_t size = list.size();

    if(list.size() > 0)
    {
        for(size_t i = 0; i < (size - 1); i++)
            o << list[i] << ", ";

        o << list[i];
    }

    return o;
}

template<typename ...Args>
std::ostream & Write(std::ostream & o, Args...)
{
    return Write(o, { Args... });
}

int main(int argc, wchar_t * argv[])
{
    std::ostringstream o;

    Write(o, 1, "Hello", 2, "World", 3, 1.4857);

    // o should contain the string of characters "1, Hello, 2, World, 3, 1.4857"

    return 0;
}

我该如何迭代每个项目并将其发送到流中?

4个回答

12

递归是一种选择:

template<typename Arg>
std::ostream & Write(std::ostream & o, Arg&& arg) { 
    return o << std::forward<Arg>(arg); 
}

template<typename Arg, typename ...Args>
std::ostream & Write(std::ostream & o, Arg&& arg, Args&&... args)
{
    o << std::forward<Arg>(arg) << ", ";
    return Write(o, std::forward<Args>(args)...);
}

演示

或者,使用扩展包小技巧仍然有效,只需要稍作修改 - 需要特判列表中的第一个项:

template<typename Arg, typename ...Args>
std::ostream & Write(std::ostream & o, Arg&& arg, Args&&... args)
{
    o << std::forward<Arg>(arg);

    using expander = int[];
    (void) expander{ (o << ", " << std::forward<Args>(args), void(), 0)... };

    return o;
}

演示


太好了,谢谢你的回答。不介意解释一下第二个(扩展器)是如何工作的以及为什么需要添加“using”关键字吗? - axsauze

3

可以编写一个辅助函数来迭代参数包:

// Invoke each of the functions `f` in sequence
template<typename... F>
void invoke_all(F&&... f) {
    std::initializer_list<bool>{(f(), false)...};
}

在您的情况下,它可以用作:
template<typename... Args>
std::ostream& Write(std::ostream& os, Args&&... args) {
    auto joiner = std::experimental::ostream_joiner(os, ", ");
    invoke_all([&]() {
        *joiner++ = std::forward<Args>(args);
    }...);
    return os;
}

2

如果在结尾处使用额外的逗号是可以接受的话,请使用

template<typename... Args>
std::ostream& Write(std::ostream& o, Args&&... args)
{
    std::initializer_list<bool> { o << std::forward<Args>(args) << ", "... };
    return o;
}

否则你需要使用递归:
template <typename A>
std::ostream& Write(std::ostream& o, A&& a)
{  return o << std::forward<A>(a);  }

template <typename A, typename... Args>
std::ostream& Write(std::ostream& o, A&& a, Args&&... args)
{
    return Write(o << std::forward<A>(a) << ", ", std::forward<Args>(args)...);
}

虽然问题没有提到 C++14,但是你可能可以使用 make_sequence<sizeof...(Args)> 来处理尾随逗号。 - Pradhan

2

这里有一个我喜欢使用的工具:

#define VARIADIC_DETAIL_CAT2(a, b) a ## b
#define VARIADIC_DETAIL_CAT(a, b) VARIADIC_DETAIL_CAT2(a, b)

#define VARIADIC_EXPAND(...) \
    int VARIADIC_DETAIL_CAT(libutil_expando, __COUNTER__) [] = { 0, \
      ((__VA_ARGS__), 0)... \
    } \
    /**/

使用这个,你可以写出以下代码:
template<typename... Args>
std::ostream & Write(std::ostream& o, Args&&... args)
{
    VARIADIC_EXPAND(o << std::forward<Args>(args));
}

获取正确的分隔符比较棘手。

我不完全确定您的解决方案中数组的好处是什么。您不能只使用((__VA_ARGS__), 0)...吗? - zneak
@zneak,您需要一个允许包扩展的上下文。花括号初始化列表就是其中之一。 - T.C.
@zneak:在C++17中使用折叠表达式可以实现这种方法。 - Eric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接