C++11 Variadic Printf Performance

C++11 Variadic Printf performance

The safe_printf function by Andrei Alexandrescu is quite clever, but unfortunately has serious limitations:

  1. Each argument is processed twice, once to check its validity and the second time to format it with printf. The check can be disabled in release mode to avoid overhead, but this seriously undermines safety.

  2. It doesn't work with positional arguments.

There is a number of ways how you can improve on it:

  1. Don't always forward formatting to printf once the argument type is established. For example, this benchmark shows that it's possible to implement integer formatting which is up to 6.7 times faster than sprintf.

  2. To implement positional arguments you need to store arguments in an array because they need to be addressed by an index.

Here's an example of how it can be done:

class Arg {
private:
enum Type { INT, DOUBLE };
Type type;
union {
int int_value;
double dbl_value;
} u;
public:
Arg(int value) : type(INT) { u.int_value = value; }
Arg(double value) : type(DOUBLE) { u.dbl_value = value; }
// other types
};

void do_safe_printf(const char *format, const Arg *args, size_t num_args) {
// here we can access arguments by index
}

template <typename... Args>
void safe_printf(const char *format, const Args&... args) {
Arg arg_array[] = {args...};
do_safe_printf(format, arg_array, sizeof...(Args));
}

Apart from supporting positional arguments, this approach will also minimize the code bloat as all the work is done by a single function do_safe_printf while safe_printf function template only places the arguments in an array.

These and other improvements have been implemented in the fmt library. According to benchmarks it is comparable or better both in speed and compiled code size to native printf implementation

Disclaimer: I'm the author of this library.

Handling variadic templates in c++11

What get<1>(t) looks like will depend on the implementation of mtuple. A typical implementation recusively inherits from a type that holds each argument, so mtuple<A,B,C> inherits from TupleHead<A> (which has a member of type A) and also inherits from TupleTail<B,C>. TupleTail<B,C> inherits from TupleHead<B> (which has a member of type B) and TupleTail<C>. TupleTail<C> inherits from TupleHead<C> (which has a member of type C.)

Now, if you give each base class an integer parameter too:

mtuple<A,B,C> inherits from TupleHead<0,A> and TupleTail<1,B,C>

TupleTail<1,B,C> inherits from TupleHead<1,B> and TupleTail<2,C>

TupleTail<2,C> inherits from TupleHead<2,C>

Now it's relatively simple to write get<1>, because the mtuple has a single unique base class of type TupleHead<1,B> which can be obtained by an upcast, then return the B member of that base class.

[Edit: get<1>(m) needs to know the type B that corresponds to the tuple element with index 1, for that you use something like std::tuple_element which also relies on the recursive inheritance hierarchy described above and uses partial specialization to get the TupleHead<1,T> base class with index 1, then determines the parameter T in that partial specialization, which gives B in my example.]

Many of the techniques used with variadic templates are functional programming techniques, such as operating on the first element of the template parameter pack, then recursively doing the same thing on the remainder of the pack, until you've processed all the elements. There aren't many things you can do with a template parameter pack directly except count its size (with sizeof...) or instantiate another template with it, so the usual approach is to instantiate another template that separates the pack Args into ArgHead, ArgsTail... and processes the head, then recursively do the same to ArgsTail

C++11: Number of Variadic Template Function Parameters?

Just write this:

const std::size_t n = sizeof...(T); //you may use `constexpr` instead of `const`

Note that n is a constant expression (i.e known at compile-time), which means you may use it where constant expression is needed, such as:

std::array<int,   n>  a; //array of  n elements
std::array<int, 2*n> b; //array of (2*n) elements

auto middle = std::get<n/2>(tupleInstance);

Note that if you want to compute aggregated size of the packed types (as opposed to number of types in the pack), then you've to do something like this:

template<std::size_t ...>
struct add_all : std::integral_constant< std::size_t,0 > {};

template<std::size_t X, std::size_t ... Xs>
struct add_all<X,Xs...> :
std::integral_constant< std::size_t, X + add_all<Xs...>::value > {};

then do this:

constexpr auto size = add_all< sizeof(T)... >::value;

In C++17 (and later), computing the sum of size of the types is much simpler using fold expression:

constexpr auto size = (sizeof(T) + ...);

Variadic templates

  1. Type-safe printf
  2. Forwarding of arbitrary many constructor arguments in factory methods
  3. Having arbitrary base-classes allows for putting and removing useful policies.
  4. Initializing by moving heterogenous typed objects directly into a container by having a variadic template'd constructor.
  5. Having a literal operator that can calculate a value for a user defined literal (like "10110b").

Sample to 3:

template<typename... T> struct flexible : T... { flexible(): T()... { } };

Sample to 4:

struct my_container { template<typename... T> my_container(T&&... t) { } };
my_container c = { a, b, c };

Sample to 5:

template<char... digits>
int operator "" b() { return convert<digits...>::value; }

See this example code: here

C++11 Variadic Templates: 3..n int's separated into first, middle, last

Use Boost.Hana (requires a C++14 compiler):

#include <boost/hana.hpp>
#include <tuple>
namespace hana = boost::hana;

template <int>
struct OtherClass { };

template <int ...I>
class MyClass {
static constexpr auto ints = hana::tuple_c<int, I...>;

OtherClass<hana::front(ints)> first;

using Middle = typename decltype(
hana::unpack(hana::slice_c<1, sizeof...(I) - 1>(ints), [](auto ...i) {
return hana::type_c<std::tuple<OtherClass<ints[i]>...>>;
})
)::type;
Middle middle;

OtherClass<hana::back(ints)> last;
};

int main() {
MyClass<0, 3, 2, 1> x;
}

Note that I'm abusing a Clang bug in the above, because lambdas are not allowed to appear in unevaluated contexts. To be standards-compliant, you should use a hand-written function object instead of the lambda when calling hana::unpack.

Also, if you go the Hana way, I would suggest that you use hana::tuple if compile-time performance is a consideration, since std::tuple is a slowpoke in all known standard library implementations.



Related Topics



Leave a reply



Submit