使用可变模板参数的boost::format

11

假设我有一个类似于printf的函数(用于记录日志),利用了完美转发:

template<typename... Arguments>
void awesome_printf(std::string const& fmt, Arguments&&... args)
{
    boost::format f(fmt);
    f % /* How to specify `args` here? */;
    BlackBoxLogFunction(boost::str(f).c_str());
}

(这不是我编写的代码,但我的实际函数遵循此指导方针)

我如何将可变参数展开到 f 变量中的 boost::format 中?


我不知道它是否有效,但你尝试过例如 args... 吗? - Some programmer dude
@JoachimPileborg 我确实尝试过这个:http://coliru.stacked-crooked.com/a/9e651d5f7532cc67,但不幸的是它不起作用(除非我做错了什么)。 - void.pointer
这就是如何扩展可变模板参数。不幸的是,Boost格式使用重载的“%”运算符来分隔参数,这将无法与扩展的参数包一起使用。 - Some programmer dude
@JoachimPileborg 嗯,它是在模板参数包和完美转发出现之前设计的,这解释了设计的很大一部分(以及它的弱点)。 - Deduplicator
我认为可以使用C++1z中提供的折叠运算符来完成。http://en.cppreference.com/w/cpp/language/fold 。相关:https://dev59.com/EF4c5IYBdhLWcg3wvsfB - Mohammad Alaggan
3个回答

14

简单总结一下void.pointer的解决方案Praetorian提出的提示, T.C.Jarod42,让我提供最终版本(在线演示

#include <boost/format.hpp>
#include <iostream>

template<typename... Arguments>
std::string FormatArgs(const std::string& fmt, const Arguments&... args)
{
    boost::format f(fmt);
    std::initializer_list<char> {(static_cast<void>(
        f % args
    ), char{}) ...};

    return boost::str(f);
}

int main()
{
    std::cout << FormatArgs("no args\n"); // "no args"
    std::cout << FormatArgs("%s; %s; %s;\n", 123, 4.3, "foo"); // 123; 4.3; foo;
    std::cout << FormatArgs("%2% %1% %2%\n", 1, 12); // 12 1 12
}

同样,正如T.C.所指出的那样, 使用自C++17以来可用的折叠表达式语法,FormatArgs函数可以被更简洁地重写

template<typename... Arguments>
std::string FormatArgs(const std::string& fmt, const Arguments&... args)
{
    return boost::str((boost::format(fmt) % ... % args));
}

哇,我喜欢这个折叠表达式......我甚至不知道它们添加了那个。太棒了。为了澄清一个问题:在您的折叠表达式解决方案中,内部括号是必需的吗?换句话说,这样会生效吗:boost::str(boost::format(fmt) % ... % args); - void.pointer
我扩展了您的演示以包括您的折叠表达式解决方案:http://coliru.stacked-crooked.com/a/bfabe441fba08d62 我还尝试删除那些内部括号,但它无法编译。虽然很有教育意义,但知道为什么会更好。诊断并没有提供太多帮助。 - void.pointer
@void.pointer,似乎将折叠表达式放在括号里是必须的且不可省略。您可以查看折叠表达式的语法描述 (“Explanation”节)。当折叠表达式展开时,其中的括号会被移除,因此,在 boost::str((fold expr)) 的情况下,我们需要额外的括号来获取折叠表达式展开后的 boost::str(expanded fold expr)。 - PolarBear

12

通常情况下,使用可变参数模板时,您可以使用递归:

std::string awesome_printf_helper(boost::format& f){
    return boost::str(f);
}

template<class T, class... Args>
std::string awesome_printf_helper(boost::format& f, T&& t, Args&&... args){
    return awesome_printf_helper(f % std::forward<T>(t), std::forward<Args>(args)...);
}

template<typename... Arguments>
void awesome_printf(std::string const& fmt, Arguments&&... args)
{
    boost::format f(fmt);

    auto result = awesome_printf_helper(f, std::forward<Arguments>(args)...);

    // call BlackBoxLogFunction with result as appropriate, e.g.
    std::cout << result;
}

C++17中,仅需使用(f % ... % std::forward<Arguments>(args));即可。


演示.


3
强制在输出末尾换行并不利于可重用性:它很容易被集成到格式字符串中,并且强制使用它会防止在其结束之前扩展该行。 - Deduplicator

11

我做了一些谷歌搜索,发现了一个有趣的解决方案:

#include <iostream>
#include <boost/format.hpp>

template<typename... Arguments>
void format_vargs(std::string const& fmt, Arguments&&... args)
{
    boost::format f(fmt);
    int unroll[] {0, (f % std::forward<Arguments>(args), 0)...};
    static_cast<void>(unroll);

    std::cout << boost::str(f);
}

int main()
{
    format_vargs("%s %d %d", "Test", 1, 2);
}

我不知道这是否是一个推荐的解决方案,但它似乎有效。我不喜欢使用static_cast的hacky方法,但在GCC上似乎必须这样做才能消除未使用变量的警告。


3
你可以通过避免创建临时变量来避免使用 using unroll = int[]; unroll{0, (f % std::forward<Arguments>(args), 0)...}; 这段代码。 - Praetorian
@Praetorian 这样做很好;不过 using 似乎是多余的... 我想知道为什么 int[] { /* ... */ }; 不起作用... - void.pointer
因为语法不允许。可能是using或者强制类型转换的原因。 - Praetorian
请注意,此技巧依赖于f % x % y;的等效于f % x; f % y;。(此外,我会将f % std::forward<Arguments>(args)转换为void,以防类型具有重载逗号运算符。) - T.C.
您也可以使用 std::initializer_list<int>{(f% forward<Args>(args), 0)...}; 来避免强制转换(以及没有参数的情况)(但需要包含一个头文件)。 - Jarod42
在展开表达式中需要使用 std::forward 吗?使用它和直接指定 args 有什么区别? - void.pointer

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