11

Suppose I have a printf-like function (used for logging) utilizing perfect forwarding:

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());
}

(I didn't compile this but my real function follows this guideline)

How can I "unroll" the variadic argument into the boost::format variable f?

void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • I don't know if it will work, but have you tried e.g. `args...`? – Some programmer dude Sep 16 '14 at 02:15
  • @JoachimPileborg I did try that: http://coliru.stacked-crooked.com/a/9e651d5f7532cc67 , it doesn't work unfortunately (unless I'm doing it wrong). – void.pointer Sep 16 '14 at 02:22
  • That's how you expand variadic template arguments. Unfortunately Boost format uses overloaded `%` operator to separate arguments, which will not work with expanded argument packs. – Some programmer dude Sep 16 '14 at 02:26
  • @JoachimPileborg Well, it was designed before template parameter-packs and perfect forwarding existed, which accounts for a good part of the design (and its weak spots). – Deduplicator Sep 16 '14 at 03:02
  • I suppose it could be done with the fold operators that come with C++1z. http://en.cppreference.com/w/cpp/language/fold . Related : http://stackoverflow.com/questions/27582862/fold-expressions-with-arbitrary-callable – Mohammad Alaggan Nov 06 '15 at 15:22

3 Answers3

14

Just to summarize the void.pointer's solution and the hints proposed by Praetorian, T.C. and Jarod42, let me provide the final version (online demo)

#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
}

Also, as it was noted by T.C., using the fold expression syntax, available since C++17, the FormatArgs function can be rewritten in the more succinct way

template<typename... Arguments>
std::string FormatArgs(const std::string& fmt, const Arguments&... args)
{
    return boost::str((boost::format(fmt) % ... % args));
}
PolarBear
  • 1,117
  • 15
  • 24
  • Wow, I love the fold expression... I didn't even know they added that. Awesome. Question for clarity: Is the inner parenthesis required in your fold expression solution? In other words, would this work: `boost::str(boost::format(fmt) % ... % args);` – void.pointer Sep 08 '17 at 21:34
  • I expanded your demo to include your fold expression solution: http://coliru.stacked-crooked.com/a/bfabe441fba08d62 I also tried removing those inner parenthesis but it doesn't compile. Would be educational to know why, though. The diagnostic doesn't help much. – void.pointer Sep 08 '17 at 21:40
  • @void.pointer, it seems that parenthesis around a fold expression is a mandatory part of it and can't be omitted. You can look at the [grammar description of fold expression](http://en.cppreference.com/w/cpp/language/fold) ("Explanation" section). When the fold expression expands, parenthesis around it are removed, so in case of boost::str((fold expr)), we need to have extra parenthesis to get boost::str(expanded fold expr) after the fold expression expands – PolarBear Sep 09 '17 at 13:16
12

As is usual with variadic templates, you can use recursion:

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;
}

Demo.


In C++17, simply (f % ... % std::forward<Arguments>(args)); will do.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 3
    Forcing the newline at the end of the output is not a good idea for reusability: It's easily integrated into the format-string, and making it mandatory precludes extending the line *before* it ends. – Deduplicator Sep 16 '14 at 03:01
11

I did some googling and found an interesting solution:

#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);
}

I don't know if this is a recommended solution but it seems to work. I don't like the hacky static_cast usage, which seems necessary to silence the unused variable warnings on GCC.

void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • 3
    You can avoid the cast by not creating a temp variable `using unroll = int[]; unroll{0, (f % std::forward(args), 0)...};` – Praetorian Sep 16 '14 at 03:32
  • @Praetorian That works out nicely; however the `using` seems superfluous... I wonder why `int[] { /* ... */ };` doesn't work... – void.pointer Sep 16 '14 at 03:38
  • Because the grammar doesn't allow it. It's either the `using` or the cast. – Praetorian Sep 16 '14 at 03:42
  • Note that this trick is dependent on `f % x % y;` being equivalent to `f % x; f % y;`. (Also, I'd cast `f % std::forward(args)` to `void` just in case the type has an overloaded comma operator.) – T.C. Sep 16 '14 at 04:55
  • You may also use `std::initializer_list{(f% forward(args), 0)...};` to avoid the cast (and the case without args)(but requires an include). – Jarod42 Sep 16 '14 at 07:17
  • Is `std::forward` needed in the expansion expression? What would be the difference between using it and specifying `args` directly? – void.pointer Sep 16 '14 at 15:43