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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Sergey Shafiev
  • 4,205
  • 4
  • 26
  • 37
  • Ok, so you can't change the signature of the function. Can you at least add to the class and the function body? – jrok Jun 14 '12 at 10:03

5 Answers5

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) {}
}
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
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.

Scrubbins
  • 150
  • 4
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.

jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • I am not quite sure if I understand "`{...}` is not an expression". Do you mean something like http://ideone.com/1lZRw ? edit: Oops, they have a too old g++: http://ideone.com/2rbBf – Sebastian Mach Jun 14 '12 at 15:13
  • @phresnel: I meant something illustrated by http://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 http://ideone.com/LzJ5x. – jpalecek Jun 14 '12 at 22:21
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);

ecatmur
  • 152,476
  • 27
  • 293
  • 366
0

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

João Augusto
  • 2,285
  • 24
  • 28