If a call to a variational function is converted to a click on pointers to arguments, ...
This is not how variational functions work. Values of arguments, not pointers to arguments, are passed after special conversion rules for built-in types (for example, char to int).
C ++ 03 §5.2.2p7:
If there is no parameter for this argument, the argument is passed in such a way that the receiving function can get the value of the argument by calling va_arg (18.7). The standard conversions lvalue-to-rvalue (4.1), array-to-pointer (4.2) and the standard conversions of the function-to-pointer (4.3) are performed in the argument expression. After these conversions, if the argument does not have arithmetic, an enumeration, a pointer, a pointer to a member, or a class type, the program is poorly formed. If the argument is of a non-POD class type (section 9), the behavior is undefined. If the argument has an integer or enumerated type that is subject to integral promotions (4.5) or a floating point type that is subject to promotion with a floating point (4.6), the value of the argument before the call is converted to an advanced type. These promotions are called default promotions.
In particular, from the above:
If the argument is of a non-POD class type (section 9), the behavior is undefined.
C ++ points to C for the definition of va_arg, while C99 TC3 §7.15.1.2p2 says:
... if the type is incompatible with the type of the actual next argument (according to the default promotion), the behavior is undefined, except for the following cases: [list of cases that do not apply here]
Thus, if you pass a class type, it must be a POD, and the receive function must apply the correct type, otherwise the behavior is undefined. This means that in the worst case, it may work exactly as you expect.
Printf will not apply the correct type for any user-defined class type, because it does not know about them, so you cannot pass the UDT class type to printf. Your foo does the same, using a char pointer instead of the correct class type.