8

Asked because of this: Default argument in c++

Say I have a function such as this: void f(int p1=1, int p2=2, int p3=3, int p4=4);

And I want to call it using only some of the arguments - the rest will be the defaults.

Something like this would work:

template<bool P1=true, bool P2=true, bool P3=true, bool P4=true>
void f(int p1=1, int p2=2, int p3=3, int p4=4);
// specialize:
template<>
void f<false, true, false, false>(int p1) {
  f(1, p1);
}
template<>
void f<false, true, true, false>(int p1, int p2) {
  f(1, p1, p2);
}
// ... and so on. 
// Would need a specialization for each combination of arguments
// which is very tedious and error-prone

// Use:
f<false, true, false, false>(5); // passes 5 as p2 argument

But it requires too much code to be practical.

Is there a better way to do this?

Community
  • 1
  • 1
Pubby
  • 51,882
  • 13
  • 139
  • 180
  • what exactly is it you want to achieve? i get the feeling whatever it is you are trying to do can be done in a simpler way. the above code looks like the beginning of a maintenance nightmare. – AndersK Nov 18 '11 at 06:10
  • @AndersK. A way to specify which arguments I want to use. I agree that it shouldn't be done, but I am still curious to if is possible. – Pubby Nov 18 '11 at 06:13
  • 1
    Do you really need it? You will have to consider (N!) variants of arguments placing. 4! = 24 definitions. It's *quite* clearer to pass all parameters – Andrey Atapin Nov 18 '11 at 06:19
  • 1
    @Andrey Atapin -- it's not n!, just a 2^n, quite a difference. – Gene Bushuyev Nov 18 '11 at 18:46
  • B.t.w. whatever the reason for this particular code is, similar cases do appear. In such cases I write C++ code generation tools to handle them, some people use m4 macroprocessor, some use VS macros. I personally prefer code generation to macros. – Gene Bushuyev Nov 18 '11 at 18:52
  • @GeneBushuyev, my bad, quite right - 2^n. Though it doesn't make fun anyway. – Andrey Atapin Nov 18 '11 at 20:03
  • apparently there was a typo. fixed. – Johannes Schaub - litb Nov 26 '11 at 19:51

4 Answers4

11

Use the Named Parameters Idiom (→ FAQ link).

The Boost.Parameters library (→ link) can also solve this task, but paid for by code verbosity and greatly reduced clarity. It's also deficient in handling constructors. And it requires having the Boost library installed, of course.

halfer
  • 19,824
  • 17
  • 99
  • 186
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Named parameters idiom is only syntactically different than a bunch of setter functions. The latter IMHO is more natural, as the chained function calls smell bad. Both approaches have the same problem, creating init type of functions as a substitute for ctor initialization. – Gene Bushuyev Nov 18 '11 at 22:18
  • 1
    Keep in mind that Parameters produces some of the worst error messages you will ever see from a compiler, if you use it for template code. – pmr Nov 26 '11 at 20:27
7

Have a look at the Boost.Parameter library.

It implements named paramaters in C++. Example:

#include <boost/parameter/name.hpp>
#include <boost/parameter/preprocessor.hpp>
#include <iostream>

//Define
BOOST_PARAMETER_NAME(p1)    
BOOST_PARAMETER_NAME(p2)
BOOST_PARAMETER_NAME(p3)
BOOST_PARAMETER_NAME(p4)

BOOST_PARAMETER_FUNCTION(
                         (void),
                         f,
                         tag,
                         (optional            
                          (p1, *, 1)
                          (p2, *, 2)
                          (p3, *, 3)
                          (p4, *, 4)))
{
    std::cout << "p1: " << p1 
            << ", p2: " << p2
            << ", p3: " << p3
            << ", p4: " << p4 << "\n";
}
//Use
int main()
{
    //Prints "p1: 1, p2: 5, p3: 3, p4: 4"
    f(_p2=5);
}
Mankarse
  • 39,818
  • 11
  • 97
  • 141
  • I like this answer a lot, although it's a bit overkill. – Pubby Nov 18 '11 at 06:36
  • Boost.Parameters is quite a bit of overkill for the task. – Cheers and hth. - Alf Nov 18 '11 at 07:19
  • @Pubby: might be, but it's interesting to delve into the generated code (`-E` on gcc) and see how they achieve this with metaprogramming. It's one of the eye-openers into the power of templates. – Matthieu M. Nov 18 '11 at 07:21
  • 1
    @Matthieu: you'll find that the metaprogramming bit is trivial but extremely tedious. and that it isn't all solved by standard language metaprogramming, but also, for C++98, by compiler-specific quirks of the preprocessor. it's one of the greatest hacks in C++ history, I think, but not something one would use except in dire need: more reasonable approaches such NPI deal much more simply and effectively with the most common cases. – Cheers and hth. - Alf Nov 18 '11 at 07:28
  • @AlfP.Steinbach: I agree regarding the preprocessor hacks, this is why I only (wished) to talk about templates and recommend looking at the preprocessor output. I am not amazed by the degree of ingeniosity of the solution, more by the idea itself. It's not something you intuitively think of when looking at templates, and yet it's so elegant *to use* (but, as you said, requires some boilerplate code to set up). – Matthieu M. Nov 18 '11 at 07:42
4

Although Boost.Parameters is amusing, it suffers (unfortunately) for a number of issues, among which placeholder collision (and having to debug quirky preprocessors/template errors):

BOOST_PARAMETER_NAME(p1)

Will create the _p1 placeholder that you then use later on. If you have two different headers declaring the same placeholder, you get a conflict. Not fun.

There is a much simpler (both conceptually and practically) answer, based on the Builder Pattern somewhat is the Named Parameters Idiom.

Instead of specifying such a function:

void f(int a, int b, int c = 10, int d = 20);

You specify a structure, on which you will override the operator():

  • the constructor is used to ask for mandatory arguments (not strictly in the Named Parameters Idiom, but nobody said you had to follow it blindly), and default values are set for the optional ones
  • each optional parameter is given a setter

Generally, it is combined with Chaining which consists in making the setters return a reference to the current object so that the calls can be chained on a single line.

class f {
public:
  // Take mandatory arguments, set default values
  f(int a, int b): _a(a), _b(b), _c(10), _d(20) {}

  // Define setters for optional arguments
  // Remember the Chaining idiom
  f& c(int v) { _c = v; return *this; }
  f& d(int v) { _d = v; return *this; }

  // Finally define the invocation function
  void operator()() const;

private:
  int _a;
  int _b;
  int _c;
  int _d;
}; // class f

The invocation is:

f(/*a=*/1, /*b=*/2).c(3)(); // the last () being to actually invoke the function

I've seen a variant putting the mandatory arguments as parameters to operator(), this avoids keeping the arguments as attributes but the syntax is a bit weirder:

f().c(3)(/*a=*/1, /*b=*/2);

Once the compiler has inlined all the constructor and setters call (which is why they are defined here, while operator() is not), it should result in similarly efficient code compared to the "regular" function invocation.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
2

This isn't really an answer, but...

In C++ Template Metaprogramming by David Abrahams and Aleksey Gurtovoy (published in 2004!) the authors talk about this:

While writing this book, we reconsidered the interface used for named function parameter support. With a little experimentation we discovered that it’s possible to provide the ideal syntax by using keyword objects with overloaded assignment operators:

f(slew = .799, name = "z");

They go on to say:

We’re not going to get into the implementation details of this named parameter library here; it’s straightforward enough that we suggest you try implementing it yourself as an exercise.

This was in the context of template metaprogramming and Boost::MPL. I'm not too sure how their "straighforward" implementation would jive with default parameters, but I assume it would be transparent.

Keith Layne
  • 3,688
  • 1
  • 24
  • 28
  • This is actually the heart of the magic behind Boost.Parameters. I've used this successfully for DSLs as well, it really creates neat interfaces. – Matthieu M. Nov 18 '11 at 07:25
  • @MatthieuM. So all I have to do is read the Boost.Parameters headers to learn this (black?) magic? While I'm at it, I'll think I'll read *all* the Boost sources. Then I can be a good C++ programmer by age 70. :) – Keith Layne Nov 18 '11 at 07:28
  • Frankly, can't say I would like to use boost.parameters, it's a curious thing, but in real use the cure is worse that the disease. I would either use code generation or (if performance not critical) a simple few lines parser and supply a string to a function: `f("slew = .799, name = \"z\"");` – Gene Bushuyev Nov 18 '11 at 19:18