type-safe varargs

T

Tim H

Goal: to provide the simplest call-site possible at the cost of
upfront hackery.

I have a function that essentially takes a variable list of arguments:

func(const string &k0, unsigned long long v0, const string &k1,
unsigned long long v1, etc)

This is going to be called a LOT from code that is hand-written, so I
really ant to simplify the job of calling this function.

Options I have found:

Use varargs.
func(...)
This has the plus of being simple and unlimited. It has the downside
of being limited to built-in types - char * rather than string. It
also has the downside of forcing the caller to make sure literal ints
are ULL, lest we unbalance the stack.


Use C pre-processor hackery to get __VA_ARGS__ to initialize an array
arg.
#define FUNC(...) FUNC_((kvpair[]){__VA_ARGS__, {NULL}})
This falls down as soon as you use anything but a literal in the
call. And the syntax confuses every syntax coloring app I have
seen :)


Use default args.
func(const string &k0, unsigned long long v0,
const string &k1="", unsigned long long v1=0,
const string &k2="", unsigned long long v2=0,
const string &k3="", unsigned long long v3=0,
const string &k4="", unsigned long long v4=0)
This has the upside that it is type-safe and simple to call. It has
the downside that I need to extend the args list to cover the worst
case caller, and pay that cost at every call.


Can anyone suggest a better design?
 
T

tragomaskhalos

Goal: to provide the simplest call-site possible at the cost of
upfront hackery.

I have a function that essentially takes a variable list of arguments:

func(const string &k0, unsigned long long v0, const string &k1,
unsigned long long v1, etc)

This is going to be called a LOT from code that is hand-written, so I
really ant to simplify the job of calling this function.

Options I have found:

Use varargs.
func(...)
This has the plus of being simple and unlimited. It has the downside
of being limited to built-in types - char * rather than string. It
also has the downside of forcing the caller to make sure literal ints
are ULL, lest we unbalance the stack.

Use C pre-processor hackery to get __VA_ARGS__ to initialize an array
arg.
#define FUNC(...) FUNC_((kvpair[]){__VA_ARGS__, {NULL}})
This falls down as soon as you use anything but a literal in the
call. And the syntax confuses every syntax coloring app I have
seen :)

Use default args.
func(const string &k0, unsigned long long v0,
const string &k1="", unsigned long long v1=0,
const string &k2="", unsigned long long v2=0,
const string &k3="", unsigned long long v3=0,
const string &k4="", unsigned long long v4=0)
This has the upside that it is type-safe and simple to call. It has
the downside that I need to extend the args list to cover the worst
case caller, and pay that cost at every call.

Can anyone suggest a better design?

How about an object to encapsulate all the parameters.

Something like (foreshortened example):
struct Params {
string& k1;
string& k2;
ull v1;
ull v2;
Params(const string& k1, const string&k2) : k1(k1), k2(k2), v1(0),
v2(0) {}
Params(const string& k1, ull v1) : k1(k1), v1(v1), v2(0) {}
Params(const string& k1, ull v1, ull v2) : k1(k1), v1(v1), v2(v2) {}
// etc - only need to cover the permutations you use
};

then.

extern void func(const Params&);
:
func(Params("foo", "bar"));
func(Params("baz", 123345ULL));
func(Params("yadda", 123344ULL, 789010ULL));

etc.
 
T

tragomaskhalos

Something like (foreshortened example):
struct Params {
string& k1;
string& k2;

Oops. Should be
const string& k1;
const string& k2;
I think this is OK wrt binding-to-temporary lifetime issues,
hopefully someone will shoot me down if not.
 
T

Tim H

Oops. Should be
const string& k1;
const string& k2;
I think this is OK wrt binding-to-temporary lifetime issues,
hopefully someone will shoot me down if not.

I still need to provide a constructor that has 32 pairs of (kn, vn) or
32 different constructors. At least with 32 different ctors, I don't
pay the perf cost. It's still hideous :)
 
K

Kira Yamato

Goal: to provide the simplest call-site possible at the cost of
upfront hackery.

I have a function that essentially takes a variable list of arguments:

func(const string &k0, unsigned long long v0, const string &k1,
unsigned long long v1, etc)

This is going to be called a LOT from code that is hand-written, so I
really ant to simplify the job of calling this function.

Options I have found:

Use varargs.
func(...)
This has the plus of being simple and unlimited. It has the downside
of being limited to built-in types - char * rather than string. It
also has the downside of forcing the caller to make sure literal ints
are ULL, lest we unbalance the stack.


Use C pre-processor hackery to get __VA_ARGS__ to initialize an array
arg.
#define FUNC(...) FUNC_((kvpair[]){__VA_ARGS__, {NULL}})
This falls down as soon as you use anything but a literal in the
call. And the syntax confuses every syntax coloring app I have
seen :)


Use default args.
func(const string &k0, unsigned long long v0,
const string &k1="", unsigned long long v1=0,
const string &k2="", unsigned long long v2=0,
const string &k3="", unsigned long long v3=0,
const string &k4="", unsigned long long v4=0)
This has the upside that it is type-safe and simple to call. It has
the downside that I need to extend the args list to cover the worst
case caller, and pay that cost at every call.


Can anyone suggest a better design?

How about defining the function as an object instead?

class func
{
public:
func &set(const string &key, unsigned long long val)
{
// do whatever you need with the parameter here.
return *this;
}

int execute()
{
// this is the actual "function" call here.
}
};

This code will support syntax like
func.set("foo", 1).set("bar", 2).execute();

Furthermore, if the keys for the parameters are known at compile time,
you can make it more efficient by declaring methods like
func &setFoo(unsigned long long val);
to make runtime more efficient.
 
P

Peter

Goal: to provide the simplest call-site possible at the cost of
upfront hackery.

I have a function that essentially takes a variable list of arguments:

func(const string &k0, unsigned long long v0, const string &k1,
unsigned long long v1, etc)

This is going to be called a LOT from code that is hand-written, so I
really ant to simplify the job of calling this function.

Options I have found:

Use varargs.
func(...)
This has the plus of being simple and unlimited. It has the downside
of being limited to built-in types - char * rather than string. It
also has the downside of forcing the caller to make sure literal ints
are ULL, lest we unbalance the stack.

Use C pre-processor hackery to get __VA_ARGS__ to initialize an array
arg.
#define FUNC(...) FUNC_((kvpair[]){__VA_ARGS__, {NULL}})
This falls down as soon as you use anything but a literal in the
call. And the syntax confuses every syntax coloring app I have
seen :)

Use default args.
func(const string &k0, unsigned long long v0,
const string &k1="", unsigned long long v1=0,
const string &k2="", unsigned long long v2=0,
const string &k3="", unsigned long long v3=0,
const string &k4="", unsigned long long v4=0)
This has the upside that it is type-safe and simple to call. It has
the downside that I need to extend the args list to cover the worst
case caller, and pay that cost at every call.

Can anyone suggest a better design?


void func(const std::vector<boost::any> &);

or if you want to change the arguments:


void func(std::vector<boost::any> &);

upside is that boost::any can encapsulate anything with a copy
constructor including POD types.
You could write simple functions for any possible argument combination
which return a vector, e.g.


std::vector<boost::any> createVectorByStringAndInt(const std::string
&_r0, const int _i1)
{ std::vector<boost::any> s;

s.push_back(_r0);
s.push_back(_i1);
return s;
}

then you could do


std::string s0;
int i1;
func(createVectorByStringAndInt(s0, i1));

Be aware that using boost::any is not free.
When creating a boost::any an object is allocated on the heap and the
passed object is copy-constructed.
Same for copying boost::any.
 
P

Peter

Goal: to provide the simplest call-site possible at the cost of
upfront hackery.

I have a function that essentially takes a variable list of arguments:

func(const string &k0, unsigned long long v0, const string &k1,
unsigned long long v1, etc)

This is going to be called a LOT from code that is hand-written, so I
really ant to simplify the job of calling this function.

Options I have found:

Use varargs.
func(...)
This has the plus of being simple and unlimited. It has the downside
of being limited to built-in types - char * rather than string. It
also has the downside of forcing the caller to make sure literal ints
are ULL, lest we unbalance the stack.

Use C pre-processor hackery to get __VA_ARGS__ to initialize an array
arg.
#define FUNC(...) FUNC_((kvpair[]){__VA_ARGS__, {NULL}})
This falls down as soon as you use anything but a literal in the
call. And the syntax confuses every syntax coloring app I have
seen :)

Use default args.
func(const string &k0, unsigned long long v0,
const string &k1="", unsigned long long v1=0,
const string &k2="", unsigned long long v2=0,
const string &k3="", unsigned long long v3=0,
const string &k4="", unsigned long long v4=0)
This has the upside that it is type-safe and simple to call. It has
the downside that I need to extend the args list to cover the worst
case caller, and pay that cost at every call.

Can anyone suggest a better design?

func(const std::vector<std::pair<std::string, unsigned long long> >
&_r);
 
T

Tim H

Goal: to provide the simplest call-site possible at the cost of
upfront hackery.
I have a function that essentially takes a variable list of arguments:
func(const string &k0, unsigned long long v0, const string &k1,
unsigned long long v1, etc)
This is going to be called a LOT from code that is hand-written, so I
really ant to simplify the job of calling this function.
Options I have found:
Use varargs.
func(...)
This has the plus of being simple and unlimited. It has the downside
of being limited to built-in types - char * rather than string. It
also has the downside of forcing the caller to make sure literal ints
are ULL, lest we unbalance the stack.
Use C pre-processor hackery to get __VA_ARGS__ to initialize an array
arg.
#define FUNC(...) FUNC_((kvpair[]){__VA_ARGS__, {NULL}})
This falls down as soon as you use anything but a literal in the
call. And the syntax confuses every syntax coloring app I have
seen :)
Use default args.
func(const string &k0, unsigned long long v0,
const string &k1="", unsigned long long v1=0,
const string &k2="", unsigned long long v2=0,
const string &k3="", unsigned long long v3=0,
const string &k4="", unsigned long long v4=0)
This has the upside that it is type-safe and simple to call. It has
the downside that I need to extend the args list to cover the worst
case caller, and pay that cost at every call.
Can anyone suggest a better design?

func(const std::vector<std::pair<std::string, unsigned long long> >
&_r);

And how does the caller call that? You can't declare a vector the
same way you can an array.

I want to see the call-site be something like:
func("key1", 1, "key2", 2, "key3, 99, "key4", -12345678);

I know it sounds silly, but trust me, it makes my life a whole lot
easier, the simpler it is.
 
J

Jim Langston

Tim said:
Goal: to provide the simplest call-site possible at the cost of
upfront hackery.
I have a function that essentially takes a variable list of
arguments:
func(const string &k0, unsigned long long v0, const string &k1,
unsigned long long v1, etc)
This is going to be called a LOT from code that is hand-written, so
I really ant to simplify the job of calling this function.
Options I have found:
Use varargs.
func(...)
This has the plus of being simple and unlimited. It has the
downside of being limited to built-in types - char * rather than
string. It also has the downside of forcing the caller to make
sure literal ints are ULL, lest we unbalance the stack.
Use C pre-processor hackery to get __VA_ARGS__ to initialize an
array arg.
#define FUNC(...) FUNC_((kvpair[]){__VA_ARGS__, {NULL}})
This falls down as soon as you use anything but a literal in the
call. And the syntax confuses every syntax coloring app I have
seen :)
Use default args.
func(const string &k0, unsigned long long v0,
const string &k1="", unsigned long long v1=0,
const string &k2="", unsigned long long v2=0,
const string &k3="", unsigned long long v3=0,
const string &k4="", unsigned long long v4=0)
This has the upside that it is type-safe and simple to call. It has
the downside that I need to extend the args list to cover the worst
case caller, and pay that cost at every call.
Can anyone suggest a better design?

func(const std::vector<std::pair<std::string, unsigned long long> >
&_r);

And how does the caller call that? You can't declare a vector the
same way you can an array.

I want to see the call-site be something like:
func("key1", 1, "key2", 2, "key3, 99, "key4", -12345678);

I know it sounds silly, but trust me, it makes my life a whole lot
easier, the simpler it is.

But varargs is not simple is it?
 
T

Tim H

Dredging up an old thread with a new answer. I wish I was less proud
of this. Is __VA_ARGS__ standard C++ or just C99?

#include <vector>
#include <iostream>

// do whatever you need in your helper
struct helper
{
public:
helper(const std::string &str): m_str(str)
{
}
const std::string &
str() const
{
return m_str;
}

private:
std::string m_str;
};

typedef std::vector<helper> helper_list;

inline helper_list
operator,(const helper &lhs, const helper &rhs)
{
helper_list tmp;
tmp.push_back(lhs);
tmp.push_back(rhs);
return tmp;
}
inline helper_list
operator,(const helper_list &lhs, const helper &rhs)
{
helper_list tmp(lhs);
tmp.push_back(rhs);
return tmp;
}

/*
* unlimited_args()
*
* It can take an "unlimited" number of arguments, in the form:
* unlimited_args((helper("abc"), helper("def")))
*
* NOTE: In calling this, the extra parens are required. Why?
* Well, C++ won't call operator,() on function arguments, so we force
* the arguments to be a single argument, which is an expression.
* Fortunately, a touch of macro magic makes it look like an unlimited
* argument list.
*/
#define unlimited_args(name, ...) unlimited_args_impl(name,
(__VA_ARGS__))

void
unlimited_args_impl(const std::string &name, const helper_list &args)
{
std::cout << name << std::endl;
for (std::size_t i = 0; i < args.size(); i++) {
std::cout << " " << args.str() << std::endl;
}
}
// With a smarter custom helper_list (less than 30 LOC), this overload
can be
// avoided.
void
unlimited_args_impl(const std::string &name, const helper &arg)
{
helper_list hl;
hl.push_back(arg);
unlimited_args_impl(name, hl);
}

int
main()
{
// these calls can take any number of helper args
unlimited_args("small", helper("a"));
unlimited_args("medium", helper("a"), helper("b"));
unlimited_args("large", helper("a"), helper("b"),
helper("c"));

return 0;
}
 
R

red floyd

Tim said:
Dredging up an old thread with a new answer. I wish I was less proud
of this. Is __VA_ARGS__ standard C++ or just C99?

Variadic macros are C99 (not sure of the exact C99 syntax), and I
believe they're slated for C++0x. However, the current (C++03)
preprocessor does not support variadic macros.
 
T

Tim H

Variadic macros are C99 (not sure of the exact C99 syntax), and I
believe they're slated for C++0x. However, the current (C++03)
preprocessor does not support variadic macros.

I compiled the above code on gcc, so no surprise. Even so, the
calling syntax could change to:

unlimited_args("foo" (helper("a"), helper("b"), helper("c"));

Tim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top