Some notes before...
Variable arguments add something essential to C. (Without them, the printf()
family couldn't hardly be implemented in pure C.) It is based on macros which close the gap between unique code and varying implementations which are very platform and hardware dependent. In C++, the use of such macros should be prevented in general as much as possible. template
s provide a type-safe alternative.
Considering that the C++ replacement for the printf()
family are the stream classes with a variety of overloaded shift operators, IMHO, in certain situations the principle with a format string and a varying number of arguments is much more appropriate (e.g. for output of notifications or log messages at runtime where, additionally localization shall be applied which even may change the output order of formatted arguments). Stream classes with shift operators cannot be used well for this. Some years ago, when we used gtkmm
I found Glib::ustring::compose()
very appropriate for this job. Though, it's (as well as printf()
) not "compile time safe" it's at least type safe and runtime checks can be easily applied. And: AFAIK, it's free of macros – implemented in pure C++ code only.
However, the above disclaimer in mind, I tried to understand/solve the issue of OP concerning an implementation with variable arguments. When writing the sample code, I just realized why it becomes a problem to call a constructor accepting va_list
out of another constructor. Following the scheme which is nicely presented in the answer to SO: Passing variable arguments to another function that accepts a variable argument list the following code forms:
struct s0 {
s0(const char *fmt, va_list args);
};
struct s1: s0 {
s1(const char *fmt, ...):
va_list args;
va_start(args, fmt);
s0(fmt, args)
va_end(args);
{ }
};
Ouch, wrong! I really would wonder if there is any platform/hardware combination where this will compile successfully. So, the call of base class constructor has to be moved into the body:
struct s1: s0 {
s1(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
s0(fmt, args);
va_end(args);
}
};
Ouch, wrong again! Beyond the fact that I even don't know wether a constructor might be called in the body of a function, I cannot prevent the implicit call of the default constructor in this case which is not available.
So, I ended up in the certainty that constructor with va_list
is not the solution in this case. I don't see any other way as separating the function which gets/processes the va_list
:
#include <iostream>
#include <cstdio>
#include <cstdarg>
struct s0 {
char name[64];
s0 *parent;
int stackLevel;
s0(s0 *parent, const char *nameMask, ...):
parent(parent)
{
va_list args;
va_start(args, nameMask);
vsnprintf(name, sizeof name, nameMask, args);
va_end(args);
}
~s0() = default;
protected:
// constructor for derived classes
s0(s0 *parent): name(""), parent(parent) { }
// construction of name
void initName(const char *nameMask, va_list args)
{
vsnprintf(name, sizeof name, nameMask, args);
}
};
struct s1: s0 {
int p1;
s1(s0 *parent, int p1, const char *nameMask, ...):
s0(parent), p1(p1)
{
va_list args;
va_start(args, nameMask);
initName(nameMask, args);
va_end(args);
}
~s1() = default;
};
int main(int argc, char *argv[])
{
s0 s0(nullptr, "s0[%d]", 0);
std::cout << "s0 '" << s0.name << "'\n";
s1 s1(&s0, 1, "s1[%d]", 1);
std::cout << "s1 '" << s1.name << "'\n";
return 0;
}
Output:
s0 's0[0]'
s1 's1[1]'
Live Demo on coliru
Note:
While implementing the sample I realized that vsprintf_s()
is part of the C11 standard but not C++11. Hence, I replaced it with std::vsnprintf()
which works sufficiently similar.