At Loss for Words

…of Programming, Love and Metaphors

sprintf-alloca-style string building for constructors

, ,

Lets say we have a COM class whose constructor is COM::COM(unsigned int index); that derives from Device class whose constructor is Device::Device(const char * devname); How do you implement the initialization in the constructor of COM?

Ladies and gentlemen, I give you the ext::sprintfa template:

COM::COM (unsigned int index)
    : Device (ext::sprintfa <16u> ("\\\\.\\COM%u", index + 1u)) {}

The implementation of the idea should be clear from the synopsis that follows. The displayed use of ext::sprintfa<X> compiles into constructor call of a stack-allocated temporary object. Static buffer (of length X) of this object acts as memory for the resulting string. Pointer to this string is retrieved by implicit conversion to const char *. And the object is automatically destroyed at the end of the expression, that is, after the full chain of constructors finishes.

template <unsigned int _Size>
class ext::sprintfa {
    private:
        char memory [_Size];
    public:
        static const unsigned int size = _Size;

    public:
        sprintfa (const char * format, ...)
            __attribute__ ((__format__ (__printf__, 2, 3)));

        ~sprintfa ();
            
    public:
        operator const char * ();
        const char * operator & ();
};
Users of compilers other than GCC will need to remove the constructor attribute.

const char * operator & (); here is a helper function that simplifies passing ext::sprintfa<> instances to functions which take variable number of parameters by simply prefixing it with an ampersand. E.g. printf-family functions or another ext::sprintfa calls.

To make this article complete, the actual implementation may look like this:

template <unsigned int _Size>
ext::sprintfa <_Size> ::sprintfa (const char * format, ...) {
    using namespace std;

    va_list args;
    va_start (args, format);
    
    vsnprintf (this->memory, _Size, format, args);
    
    va_end (args);
    return;
};

template <unsigned int _Size>
ext::sprintfa <_Size> ::~sprintfa () {
    if (_Size)
        this->memory [0] = '\0';
    return;
};

template <unsigned int _Size>
ext::sprintfa <_Size> ::operator const char * () {
    return this->memory;
};

template <unsigned int _Size>
const char * ext::sprintfa <_Size> ::operator & () {
    return this->memory;
};

In real world code you would probably want to use wchar_t/vswprintf. To implement such ext::swprintfa template is left as an exercise to the reader :)

Conservative variable size memory sharingVosková zátoka