views:

497

answers:

4

This C++ question seems to be pretty basic and general but still I want someone to answer.

1) What is the difference between a function with variable-length argument and an overloaded function? 2) Will we have problems if we have a function with variable-length argument and another same name function with similar arguments?

+1  A: 

1) Well an overloaded function will require a HELL of a lot of different prototypes and implementations. It will also be type safe.
2) Yes this will cause you problems as the compiler will not know which function it needs to call. It may or may not warn about this. If it doesn't you may well end up with hard to find bugs.

Goz
`+1` for `1)` :)
Johannes Schaub - litb
Regarding 1) - templates, anyone?
DevSolar
Templates only allow to parameterize the type of a parameter. They don't allow to parameterize the amount of parameters of a function (this restriction is liften in c++0x, which has variadic templates).
Johannes Schaub - litb
templates are handy when it is any type that can be passed in but when it is a variable number of same type parameters it becomes less useful :D
Goz
Ah, that's the way you meant... see my answer for how to "string together" parameters by overloading operator<<() / operator>>(). It's not the same syntax as (...) I admit, but it's common C++ style and does require only one overloading per accepted input type.
DevSolar
Personally i'm a huge hater of the << and >> syntax. I refuse to use it. I really hate the idea that an operator can be used like that. It doesn't fit with the logic of the shift operator, IMHO ... Each to their own though ...
Goz
Well, for most C++ proggers "<<" is the output operator first. I actually heard a coworker utter "you can use it like that, too?" when I showed him an algorithm using bit shifting. ;-) I like the flexibility it gives.
DevSolar
+5  A: 

2) Do you mean the following?

int mul(int a, int b);
int mul(int n, ...);

Let's assume the first multiplies 2 integers. The second multiplies n integers passed by var-args. Called with f(1, 2) will not be ambiguous, because an argument passed through "the ellipsis" is associated with the highest possible cost. Passing an argument to a parameter of the same type however is associated with the lowest possible cost. So this very call will surely be resolved to the first function :)


Notice that overload resolution only compares argument to parameter conversions for the same position. It will fail hard if either function for some parameter pair has a winner. For example

int mul(int a, int b);
int mul(double a, ...);

Imagine the first multiplies two integers, and the second multiplies a list of doubles that is terminated by a 0.0. This overload set is flawed and will be ambiguous when called by

mul(3.14, 0.0);

This is because the second function wins for the first argument, but the first function wins for the second argument. It doesn't matter that the conversion cost for the second argument is higher for the second function than the cost of the first argument for the first function. Once such a "cross" winner situation is determined, the call for such two candidates is ambiguous.

Johannes Schaub - litb
yeah your killer goes if someone calls f( 1, 2.0f ); and that is valid ... does that go through the variable parameter list or does it cast the float to an int and go through the other function?
Goz
passing through ellipsis is the highest possible cost. It will always be the last choice. Converting float to int is a standard conversion. It's somewhere between `int -> int` and `int -> ellipsis` :)
Johannes Schaub - litb
actually, mul(int,int) will be preferred, the ellipsis has the lowest priority for overload resolution so a cast is preferred if any legitimate cast exists.
Matthieu M.
cheers litb ... but all goes to kinda illustrate my point that bugs you may not be expecting may turn up ;)
Goz
@Goz, i agree. situations can be weird. So it may not be the best idea to overload too much with vastly different parameter lists and get situation out of hand :)
Johannes Schaub - litb
A: 

It is pretty general, and Goz has already covered some of the points. A few more:

1) A variable argument list gives undefined behavior if you pass anything but POD objects. Overloaded functions can receive any kind of objects.

2) You can have ambiguity if one member of an overload set takes a variable argument list. Then again, you can have ambiguity without that as well. The variable argument list might create ambiguity in a larger number of situations though.

The first point is the really serious one -- for most practical purposes, it renders variable argument lists purely a "legacy" item in C++, not something to even consider using in any new code. The most common alternative is chaining overloaded operators instead (e.g. iostream inserters/extractors versus printf/scanf).

Jerry Coffin
+1  A: 

An overloaded function can have completely different parameter types, including none, with the correct one being picked depending on the parameter types.

A variable-length argument requires at least one parameter to be present. You also need some mechanism to "predict" the type of the next parameter (as you have to state it in va_arg()), and it has to be a basic type (i.e., integer, floating point, or pointer). Common techniques here are "format strings" (as in printf(), scanf()), or "tag lists" (every odd element in the parameter list being an enum telling the type of the following even element, with a zero enum to mark the end of the parameter list).

Generally speaking, overloading is the C++ way to go. If you end up really needing something akin to variable-length argument lists in C++, for example for conveniently chaining arguments of various number and type, consider how C++ streams work (those concatenated "<<" and ">>"s):

class MyClass {
    public:
        MyClass & operator<<( int i )
        {
            // do something with integer
            return *this;
        }

        MyClass & operator<<( double d )
        {
            // do something with float
            return *this;
        }
};

int main()
{
    MyClass foo;
    foo << 42 << 3.14 << 0.1234 << 23;
    return 0;
}
DevSolar