views:

92

answers:

3

I'm currently working on cleaning up an API full of function templates, and had a strong desire to write the following code.

template <typename T, typename U, typename V>
void doWork(const T& arg1, const U& arg2, V* optionalArg = 0);

When I invoke this template, I would like to do so as follows.

std::string text("hello");
doWork(100, 20.0, &text);
doWork('a', text);         // oops!
doWork<char, std::string, void>('a', text);  // to verbose!

Unfortunately, the second invocation doesn't compile since the compiler cannot deduce the type of the optional parameter. This is unfortunate, since I really don't care what the parameter type is, but rather that its value is NULL. Also, I'd like to avoid the route of the third invocation since it hampers readability.

This lead me to try to make the template argument V have a default type, which also doesn't work since you cannot apply a default type to a function template argument (at least using VC++ 9.0).

template <typename T, typename U, typename V = void>  // oops!
void doWork(const T& arg1, const U& arg2, V* optionalArg = 0);

My only remaining option is to introduce an overload of doWork that knows nothing of the template argument V.

template <typename T, typename U>
void doWork(const T& arg1, const U& arg2)
{
    doWork(arg1, arg2, 0);
}

template <typename T, typename U, typename V>
void doWork(const T& arg1, const U& arg2, V* optionalArg);

Is this the best approach to solving this problem? The only drawback I see is that I could potentially introduce many trivial forwarding functions if a function template contains many parameters that have suitable defaults.

+5  A: 

I think that your forwarding function is a perfectly suitable solution, although in your solution, won't you have to explicitly specify the template parameters? (0 is an integer constant that can be coverted to any V* type.) Also doWord vs doWork?

As a general rule, try to avoid optional parameters where they don't have a very strong pay-off.

It might be easier to force clients of you function to just add a , (void*)0 if appriopriate than add to much extra mechanism to support both a two parameter and a three parameter version of the template. It depends on the expected uses, though.

Charles Bailey
Yes, you are right. The type of `0` should be specified in the forwarding function.(doWord: typo)
Steve Guidi
A: 

One possibility is to reorder the template arguments, so the optional one comes first.

template <typename V, typename T, typename U>
void doWork(const T& arg1, const U& arg2, V* optionalArg = 0);

doWork<void>('a', text);

Forwarding looks OK too.

But it seems default arguments and templates don't match well, though.

UncleBens
+2  A: 

From the client code's point of view, if it doesn't have a third parameter, why does it need to invent one?

So if you aim for usability and readability, I agree with your wrapper method: it makes perfect sense, and the wrapper you wrote is responsible for a decent value of the third parameter you necessitated.

On top of that, it makes it possible to use a different default for different specializations, if needed.

xtofl
ISTR that Francis Glassborow once said that the reason default function arguments were invented in the first place despite overloading and (inline) forwarding was to help preventing code duplication arising from the problem that constructors could not forward to each other. So, except for constructors, it is never necessary. (Note that in C++1x, constructors will be able to forward to each other.)
sbi