0

While attempting to upgrade the following function to C++11

int inline formatText(const char* const fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    int len = std::min(vsnprintf(m_text, m_textSize, fmt, args), m_textSize);
    va_end(args);
    m_text[len] = '\0'; // assume m_textSize is storage size - sizeof('\0')
    return len;
}

Obviously since printf deals with PODs, it's not really a problem for this function to accept its arguments by value.

But I realized I wasn't clear on how to achieve macro-like exact forwarding of the arguments, I realize that by inlining a simple version of this the compiler can eliminate pass-by-value, but I'm not exactly sure which of the three following approaches is technically best:

template<typename... Args>
  #if defined(APPROACH_1)
int formatText(const char* const fmt, Args...)
  #elif defined(APPROACH_2)
int formatText(const char* const fmt, const Args&...)
  #else
int formatText(const char* const fmt, Args&&...)
  #endif
{
    int len = std::min(snprintf(m_text, m_textSize, fmt, std::forward<Args>(args)...);
    m_text[len] = '\0';
    return len;
}

Since we're talking printf here, copy-by-value isn't terrible, because I shouldn't be passing it non-pod objects; specifying const ref would certainly help complex objects that I don't want copying but clearly is counter-productive for the normal use cases of printf. I'm plain not sure what the side-effects of approach 3 are.

Everything I've read so far has left me with the impression that 'Args&&...' is likely to be the normal case for variadic templates, but in this case my gut tells me to go for approach #1.

kfsone
  • 23,617
  • 2
  • 42
  • 74
  • Just curious: why are any of the three better than the old varargs way? – John Zwinck Feb 02 '14 at 08:09
  • The varargs form is split into two function calls, first calling formatText and repackaging the arguments, then calling vsnprintf. The variadic template does it in a single call reducing the number of instructions by nearly half. – kfsone Feb 02 '14 at 08:32
  • And boosts performance by more than 2x. I benchmarked this piece of code (http://ideone.com/ybQaWh) using g++ 4.8.1 with `-Wall -O3 -ggdb -std=c++11 -o test.exe test.cpp`. Then I benchmarked it with `time ./test.exe >/dev/null`. On my test box, the average for ALG1 was 6.3009 wall seconds, 5.245 user, whereas the templates variant was 2.006 wall seconds, 2.004 user. – kfsone Feb 02 '14 at 08:35
  • 1
    read this - http://stackoverflow.com/questions/3582001/advantages-of-using-forward/3582313#3582313. In short variant with && seems to be best... – rAndom69 Feb 02 '14 at 08:41
  • as a sidenote: gcc may not inline functions with elipsis, so that may be your performace gain (however I'm surprised it's 3x faster as all that snprintf does is calling vsnprintf :)) At least with gcc you will get compile time validation of arguments passed vs format string, which is IMHO worth the effort (not the unexplainable performance gain) – rAndom69 Feb 02 '14 at 08:56
  • Forcing the old-style varargs version to inline makes it slower (6.564 real 5.444 user) while the template version comes out the same. (1.9-2.0 real and about the same user). But that's kinda my point, the template version makes for a nice inlining for this kind of pass-thru helper. – kfsone Feb 02 '14 at 09:02
  • @rAndom69: you don't need templates to have GCC validate the arguments vs. the format. You can use the old varargs approach and GCC's __attribute__((format, printf)) hint, which works well. – John Zwinck Feb 02 '14 at 13:31
  • @John Zwinck Part of the reason I started the experiment was to see if I retained the printf checking with the attribute version and was pleased to find that it did and it also provided a performance boost making the switch. – kfsone Feb 03 '14 at 04:35

0 Answers0