(among other advantages, the cblib version doesn't pull vector.h or windows.h into the apf headers, both of which I consider to be very severe sins)
See older posts for a description of how it works and earlier not-good way of doing it and initial announcement .
The basic way it works is :
- non-basic types are converted into basic types by the template using autoArgConvert; base types are passed through and others get
ToString() called on them.
- "%a" in the format string is turned into the type of the variable it points at (eg %d , %s, whatever). So you can of course use
"%7a" or anything else that printf can handle.
- I just call normal printf to do the actual printing.
that's it, very simple!
Here's my main.cpp for an example of usage :
#include<
float.h > #include<
stdio.h > #include<
string.h > //#include "autoprintf.h" #include "apf.h" #include<
windows.h > struct Vec3 { float x,y,z; }; namespace apf { inline const String ToString( const Vec3 & v ) { return StringPrintf("{%.3f,%.3f,%.3f}",v.x,v.y,v.z); } /* inline size_t autoArgConvert(const HWND arg) { return (size_t)arg; } */ inline const String ToString( const HWND v ) { return StringPrintf("[%08X]",v); } }; int main(int argc,const char * argv[]) { //MakeAutoPrintfINL(); //autoprintf("test bad %d",3.f); autoprintf("hello %-7a",(size_t)400,"|\n"); //* //autoprintf("100%"); autoprintf("percent %% %s","100%"," stupid","!\n"); autoprintf("hello ","world %.1f",3.f,"\n"); autoprintf("hello ",7," world\n"); autoprintf("hello %03d\n",7); autoprintf("hello %d",3," world %.1f\n",3.f); autoprintf("hello ",(size_t)400,"\n"); autoprintf("hello ",L"unicode is balls"," \n"); autoprintf("hello %a ",L"unicode is balls"," \n"); //autoprintf("hello %S ",L"unicode is balls"," \n"); autoprintf("hello ",apf::String("world")," \n"); // autoprintf("hello ",LogString()); // compile error autoprintf("top bit ",(1UL<
<
31)," \n"); autoprintf("top bit %d",(1UL<
<
31)," \n"); autoprintf("top bit %a",(1UL<
<
31)," \n"); autoprintf("top bit %a\n",(size_t)(1UL<
<
31)); HANDLE h1 = (HANDLE) 77; HWND h2 = (HWND) 77; autoprintf("HANDLE %a\n",h1); autoprintf("HWND %a\n",h2); char temp[1024]; autosnprintf(temp,1023,"hello %a %a %a",4.f,7,apf::String("world")); printf("%s\n",temp); Vec3 v = { 3, 7, 1.5f }; autoprintf("vector ",v," \n"); autoprintf("vector %a is cool %a\n",v,(size_t)100); /**/ return 0; }
The normal way to make user types autoconvert is to add a ToString() call for your type, but you could also use autoArgConvert. If you use autoArgConvert, then you will wind up going through a normal %d or whatever.
One nice thing is that this autoprintf is actually even safer than my old safeprintf. If you mismatch primitive types, (eg. you put a %d in your format string but pass a float), it will check it using the same old safeprintf method (that is, a runtime failure). But if you put a std::string in the list when you meant to put a char *, you will get a compile error now, which is much nicer.
Everything in cblib now uses this (I made Log.h be an autoprintf) and I haven't noticed a significant hit to compile time or exe size since the templates are all now deterministic and non-recursive.
Yes it does a lot of dynamic allocation. Get with the fucking 20th century. And it's fucking printf. Printf is slow. I don't want to hear one word about it.
Cool stuff. I suppose variadic templates would make the implementation a lot cleaner and avoid the autogenerated code, but it doesn't seem to be too bad anyway.
ReplyDeleteSome comments:
- On GNU's libc (that conforms with C99) vsnprintf does not return -1 when truncation occurs, but the size that the string would need to have for the operation to complete succesfully. See "Return value" in the man pages, and my implementation of format for how to use that.
- On GCC, when passing a va_list to another function you have to copy it with va_copy (which is not even defined in msvc). Otherwise it will crash on some platforms.