16

If I have a constructor with say 2 required parameters and 4 optional parameters, how can I avoid writing 16 constructors or even the 10 or so constructors I'd have to write if I used default parameters (which I don't like because it's poor self-documentation)? Are there any idioms or methods using templates I can use to make it less tedious? (And easier to maintain?)

Kyle
  • 4,487
  • 3
  • 29
  • 45
  • 1
    Are you sure your class isn't trying to do too much? – John Dibling Apr 23 '10 at 18:20
  • @Johannes: He wants the possibility to skip say d without forcing the caller to know its default value. – Danvil Apr 23 '10 at 18:24
  • 2
    @John: I can see what you're getting at, but parameters can be properties. For example, I have a GUI Button class that has lots of properties such as image, text, size, color, and so on, but I don't feel it's "doing too much." IMHO, Buttons serve a very specific purpose and are easy to fit in a single class where the user is going to know right away what the class accomplishes. – Kyle Apr 23 '10 at 18:32
  • Gotcha. I'll post an idea for you. – John Dibling Apr 23 '10 at 18:37
  • Actually the posted suggestions are more or less what I was going to suggest. – John Dibling Apr 23 '10 at 18:38
  • No user-defined templates required. C++17 has a nifty template called std::optional that serves the purpose. See answer below. – Jive Dadson Jan 20 '18 at 06:46

4 Answers4

35

You might be interested in the Named Parameter Idiom.

To summarize, create a class that holds the values you want to pass to your constructor(s). Add a method to set each of those values, and have each method do a return *this; at the end. Have a constructor in your class that takes a const reference to this new class. This can be used like so:

class Person;

class PersonOptions
{
  friend class Person;
  string name_;
  int age_;
  char gender_;

public:
   PersonOptions() :
     age_(0),
     gender_('U')
   {}

   PersonOptions& name(const string& n) { name_ = n; return *this; }
   PersonOptions& age(int a) { age_ = a; return *this; }
   PersonOptions& gender(char g) { gender_ = g; return *this; }
};

class Person
{
  string name_;
  int age_;
  char gender_;

public:
   Person(const PersonOptions& opts) :
     name_(opts.name_),
     age_(opts.age_),
     gender_(opts.gender_)
   {}
};
Person p = PersonOptions().name("George").age(57).gender('M');
Fred Larson
  • 60,987
  • 18
  • 112
  • 174
  • How would you make that work in a constructor? By using `return this` in the constructor method? – Robert Harvey Apr 23 '10 at 18:21
  • 1
    Obviously, you can't return anything from a constructor. But you can use a copy, such as `Foo f = Foo().option1().option2();` for example. The example in the link uses a separate class to do the actual creation. – Fred Larson Apr 23 '10 at 18:27
  • 2
    @Robert it's important to notice that the right side starts with `PersonOptions`, not `Person`. This creates a presumably lightweight object, and then has a number of method calls to set properties to non-default values. Then there's a ctor named `Person(const PersonOptions&)` and ta da. An object's instantiation (e.g. `PersonOptions()`) leaves "*this" in the current context so you can .method() it right away. Each method still needs to `return *this` to be able to chain after it, though. – dash-tom-bang Apr 23 '10 at 18:48
  • 2
    Couldn't you also make an Options class inside the definition of Person? So the example would be `Person p = Person::Options().name("George").age(57).gender('M');` – Mark Ransom Apr 23 '10 at 19:02
  • @Mark Ransom: I don't see any reason why not. – Fred Larson Apr 23 '10 at 19:17
  • 2
    I think the code example is missing `return *this` after `name_ = n;` and so on? – Kyle Apr 23 '10 at 19:25
9

What if you made a parameter object that contained all the fields? Then you could just pass that, and just set whichever fields you need. There's probably a name for that pattern, not sure what it is though...

UPDATE:

Code might look like somewhat this:

paramObj.x=1;
paramObj.y=2;
paramObj.z=3;
paramObj.magic=true;
... //set many other "parameters here"

someObject myObject = new someObject(paramObj);

and inside the someObject constructor you can set defaults for things that were not already set (or raise an error if it was mandatory).

Honestly, I'm not a big fan of this solution, but I've used it once or twice when paramObj made sense by containing a set of data that usually all went together (so we could use it for more than just constructors), and it was better than multiple constructors. I found that it was ugly but it worked, YMMV.

FrustratedWithFormsDesigner
  • 26,726
  • 31
  • 139
  • 202
  • 2
    ... and this parameter object would set default values in its default constructor. – Danvil Apr 23 '10 at 18:22
  • 1
    Why is this better than just constructing the original object and setting properties? – Robert Harvey Apr 23 '10 at 18:25
  • @Johannes: Isn't that just the Named Parameter Idiom that Fred Larson mentions in his answer? – Robert Harvey Apr 23 '10 at 18:27
  • @Robert, right, didn't read the linked page carefully. One more argument to ask for a short code summary of what is linked to :) – Johannes Schaub - litb Apr 23 '10 at 18:31
  • @Robert: It's only marginally better, but if the values are often passed around as a group, it sometimes makes sense to bundle them together into a single structure. – FrustratedWithFormsDesigner Apr 23 '10 at 18:35
  • 5
    @Robert: the advantage is that your main object can be immutable, which is often a goal one strives for in OO design. Also, even if your class is mutable, it obviates the need to verify that the setters for these "parameters" aren't called out of order or at times when they don't make sense given the internal state of the object. – rmeador Apr 23 '10 at 18:42
  • 2
    @Robert, it's also helpful when the parameters must be validated as a group, i.e. there are certain combinations that would be invalid. – Mark Ransom Apr 23 '10 at 20:06
4

And now for the "Boost has something for it" answer:

The Boost Parameter Library seems to be a good fit to your use case.

Éric Malenfant
  • 13,938
  • 1
  • 40
  • 42
2

All new for C++17

#include <optional>

using optional_int = std::optional<int>;

class foo {
    int arg0, arg1; // required
    int arg2, arg3; // optional
    const int default_2 = -2;
    const int default_3 = -3;
public:
    foo(int arg0, int arg1, optional_int opt0 = {}, optional_int opt1 = {})
        : arg0(arg0), arg1(arg1)
        , arg2(opt0.value_or(default_2))
        , arg3(opt1.value_or(default_3))
    { }

};

int main() {
    foo bar(42, 43, {}, 45); // Take default for opt0 (arg2)
    return 0;
}

I have a cubic spline implementation that allows the user optionally to specify the first derivative at either the left end, the right end, or both. If a derivative is not specified, then the code in effect calculates one, by assuming that the second derivative is zero (the so-called "natural spline"). Here is a fragment for the left end.

// Calculate the second derivative at the left end point
    if (!left_deriv.has_value()) {
        ddy[0]=u[0]=0.0; // "Natural spline"
    } else {
        const real yP0 = left_deriv.value();
        ddy[0] = -0.5;
        u[0]=(3.0/(x[1]-x[0]))*((y[1]-y[0])/(x[1]-x[0])-yP0);
    }
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65