4

I've got the class member:

LineND::LineND(double a ...) { coefficients.push_back(a); va_list arguments; va_start(arguments, a); double argValue; do { argValue = va_arg(arguments, double); coefficients.push_back(argValue); }while(argValue != NULL); // THIS IS A PROBLEM POINT! va_end(arguments); } 

I don't know how many arguments will be used. I need to take each argument and put it into the vector called coefficients. How should I do that? I understand, that the statement while(argValue != NULL) is not correct in this case. I can't use for example this signature:

LineND::LineND(int numArgs, double a ...) 

to change the condition like this:

while(argValue != numArgs); 

The point is I can't change the signature of the method. Need to resolve this problem another way.

1
  • Ok, so you can't change the signature of the function. Can you at least add to the class and the function body? Commented Jun 14, 2012 at 10:03

5 Answers 5

16

Variable argument lists have several drawbacks:

  • Callers can pass in everything they want.
  • If a non-POD object is passed, undefined behaviour is summoned
  • You can't rely on the number of arguments (the caller can make errors)
  • You put a LOT of responsibility on your CLIENT, for whom you intended to have an easier time with your library code (practical example: format-string-bugs/-errors)

Compared to variadic templates:

  • Compile time list size is known
  • Types are known at compile time
  • You have the responsibility for stuff, not your client, which is like it should be.

Example:

void pass_me_floats_impl (std::initializer_list<float> floats) { ... } 

You can put this into the private section of a class declaration or in some detail namespace. Note: pass_me_floats_impl() doesn't have to be implemented in a header.

Then here's the nice stuff for your client:

template <typename ...ArgsT> void pass_me_floats (ArgsT ...floats) { pass_me_floats_impl ({floats...}); } 

He now can do:

pass_me_floats (); pass_me_floats (3.5f); pass_me_floats (1f, 2f, 4f); 

But he can't do:

pass_me_floats (4UL, std::string()); 

because that would emit a compile error inside your pass_me_floats-function.

If you need at least, say, 2 arguments, then make it so:

template <typename ...ArgsT> void pass_me_floats (float one, float two, ArgsT... rest) {} 

And of course, if you want it a complete inline function, you can also

template <typename ...ArgsT> void pass_me_floats (ArgsT... rest) { std::array<float, sizeof...(ArgsT)> const floaties {rest...}; for (const auto f : floaties) {} } 
Sign up to request clarification or add additional context in comments.

Comments

3

Variadic arguments are heavily frowned upon in C++, and for good reason. One of these reasons is that there is no way to know where the list ends, so if you can't change the signature of the function, you must dictate some kind of sentinel value to indicate where the list ends. If you cannot do either of those things, you are screwed.

Comments

1

You should rewrite the method to take std::initializer_list<double>.

If you can't do that, and can't add a count parameter, the fix is to take a sentinel value that terminates the argument list, e.g. 0.0 or NaN (or any value that doesn't make sense in your function's context).

For example, functions that take a variable number of pointers use NULL as the sentinel value; functions that take a list of structs take a 0-initialised struct as the sentinel. This is fairly common in C APIs; see http://c-faq.com/varargs/nargs.html where the example given is execl("/bin/sh", "sh", "-c", "date", (char *)NULL);

Comments

1

In this case, you cannot determine the number of arguments in your code. You have to make the caller of such function pass the number of doubles to you (I'm assuming the first a argument doesn't contain the number of arguments). Eg.:

LineND::LineND(unsigned count, ...) { va_list arguments; va_start(arguments, a); 

and the condition becomes

while (a--) { ... } 

In C++11, you can use two better devices for passing unspecified number of doubles.

  1. passing a vector and initializing it with an initializer-list
  2. variadic templates

Note that there are differences between the two; tho former has some problems when forwarding originating from the fact that {...} is not an expression in C++. The latter can cause code bloat, because it generates a function for each combination of argument types.

In C++03, you can only use helper objects, such as those offered by the Boost.Assignment library, for passing unknown number of arguments.

2 Comments

I am not quite sure if I understand "{...} is not an expression". Do you mean something like ideone.com/1lZRw ? edit: Oops, they have a too old g++: ideone.com/2rbBf
@phresnel: I meant something illustrated by ideone.com/mKeCp (note that the warning is actually an error by the c++ standard and will not get through newer gcc): That { ... } is merely a special syntax used for initialization, not an expression in itself (in the C++ gramamr). You can't conveniently save it for later in a variable, you can't deduce type from it etc. Your example only uses initializer-list in the most mundane form when initializing a local variable, which avoids all the problems. For another inconsistency, see ideone.com/LzJ5x.
0

Make the first double of the arguments list be the actual count of arguments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.