Question

There are several implementations of variadic templates printf function. One is this:

void printf(const char* s) {
  while (*s) {
    if (*s == '%' && *++s != '%') 
      throw std::runtime_error("invalid format string: missing arguments");
    std::cout << *s++;
  }
}

template<typename T, typename... Args>
void printf(const char* s, const T& value, const Args&... args) {
  while (*s) {
    if (*s == '%' && *++s != '%') {
      std::cout << value;
      return printf(++s, args...);
    }
    std::cout << *s++;
  }
  throw std::runtime_error("extra arguments provided to printf");
}

and everywhere is said that this implementation is type-safe while the normal C (with variadic arguments va_arg) isn't.

Why is that? What does it mean to be type-safe and what advantages has this implementation over a C printf va_arg one?

Was it helpful?

Solution 2

Being safe, or type-safe, means that you can tell from looking at the source code whether your program behaves correctly.

The statement std::cout << x is always correct, assuming that x has a well-defined value (and isn't, say, uninitialized); this is something you can guarantee from looking at the source code.

By constrast, C is not safe: For example, the following code may or may not be correct, depending on the runtime input:

int main(int argc, char * argv[])
{
    if (argc == 3)
        printf(argv[1], argv[2]);
}

This is correct if and only if the first argument is a valid format string containing precisely one "%s".

In other words, it is possible to write a correct C program, but it's impossible to reason about the correctness just by inspecting the code. The printf function is one such example. More generally, any function that accepts variable arguments is most likely unsafe, as is any function that casts pointers based on runtime values.

OTHER TIPS

For all of the arguments you pass to the variadic template version, their types are known at compile time. This knowledge is preserved within the function. Each object is then passed to cout with the heavily overloaded operator<<. For each type that is passed, there is a separate overload of this function. In other words, if you pass an int, it's calling ostream::operator<<(int), if you pass a double, it's calling ostream::operator<<(double). So again, the type is preserved. And each of those functions is specialized to handle each type in an appropriate way. That's type safety.

With the C printf though, the story is different. The type is not preserved inside the function. It needs to figure it out based on the contents of the format string (which may be a runtime value). The function just has to assume that a correct format string was passed in to match the argument types. The compiler does not enforce this.

There is another kind of safety too, and that is in the number of arguments. If you pass too few arguments to the C printf function, not enough to match the format string, you have undefined behavior. If you do the same thing with the variadic template, you get an exception, which, while not desirable, is a much easier problem to diagnose.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top