C++: the Named Parameter Idiom
Some times you will have a large C++ class with many parameters that need initializing. That can lead to some ugly constructor calls:
auto popsim = PopulationSim(1000, 100000, 1000, 1000, 1500, 0.17, 0.05, 32, 3, 1, 7, 1);
This makes the code very hard to maintain and debug, as there is no easy way to determine which parameter means what.
“But wait – you say – in Python I can just use named parameters! Surely C++ can manage something like:
popsim = PopulationSim(
N0 = 1000,
Nmax = 100000,
M0 = 1000,
M1 = 1000,
M2 = 1500,
fishing_rate_init = 0.17,
fishing_rate_inc = 0.05,
B = 64,
T = 3,
M = 1,
R = 7,
b = 1);
Unfortunately, this is not the case. This is because C++ has no native support for named parameters, which is what we are making use of here. So, how can we come up with a similar method? In JavaScript, we would something along these lines:
var popsim = PopulationSim({
N0: 1000,
Nmax: 100000,
M0: 1000,
M1: 1000,
M2: 1500,
fishing_rate_init: 0.17,
fishing_rate_inc: 0.05,
B: 64,
T: 3,
M: 1,
R: 7,
b: 1});
What we are doing here is passing one argument, which is an object containing all the constructor’s parameters. This is a fairly neat solution, and allows us to use JavaScript Object Notation (JSON).
So let’s make a first attempt in C++, following Javascript’s example: we create a Plain Old Data (POD) struct
inside the class PopulationSim
, containing all the data we need for intialization, and then define the PopulationSim
constructor accordingly:
class PopulationSim {
// Member declarations
// ...
public:
// Struct for parameters
struct SimParams {
pop_t N0; // Initial population size
pop_t Nmax; // Maximum population size
year_t M0; // Year when mutations stop occurring
year_t M1; // Year when fishing begins
year_t M2; // Year when fishing increases
rate_t fishing_rate_init; // Initial fishing rate
rate_t fishing_rate_inc; // Increase in fishing rate at year M2
gensize_t B; // Genome size for Animals
gensize_t T; // Threshold
gensize_t M; // Mutation rate of the population
age_t R; // Minimum age for reproduction
rate_t b; // Birth rate
};
// Constructor with initializer list:
PopulationSim(const SimParams & p) :
N0_(p.N0), Nmax_(p.Nmax),
M0_(p.M0), M1_(p.M1), M2_(p.M2),
fishing_rate_init_(p.fishing_rate_init), fishing_rate_inc_(p.fishing_rate_inc),
B_(p.B), T_(p.T), M_(p.M), R_(p.R), b_(p.b), cur_year_(0),
fishing_rate_(p.fishing_rate_init) {
// Further work...
};
};
The issue with this approach is that we still can’t initialize the SimParams
object as we would like. Aggregate initialization comes real close but not quite – we still can’t use named parameters! The good news is that a similar feature is coming soon (ish): according to cppreference.com and Wikipedia, the draft for C++20 has something called designated initializers. This is a form of aggregate initialization, and would achieve what we hope for:
PopulationSim::SimParams params = {
.N0 = 1000,
.Nmax = 100000,
.M0 = 1000,
.M1 = 1000,
.M2 = 1500,
.fishing_rate_init = 0.17,
.fishing_rate_inc = 0.05,
.B = 64,
.T = 3,
.M = 1,
.R = 7,
.b = 1
};
auto sim = PopulationSim(params);
This is all very nice, but useless in practice, as we won’t be able to use C++20 for some time yet. So what’s the solution for today?
The Named Parameter Idiom
This is, in my opinion, a pretty neat trick. Even though is does require some extra work, it will make the interface for our class much nicer. Effectively, we create a series of methods, one for each parameter, which take a single argument, wet the corresponding parameter’s value, and then return a reference to *this
. Using this trick, we can now easily and clearly initialize our SimParams
object:
auto p = PopulationSim::SimParams()
.N0(100)
.Nmax(10000)
.M0(1000)
.M1(1000)
.M2(1500)
.fishing_rate_init(0.17)
.fishing_rate_inc(0.05)
.B(64)
.T(3)
.M(1)
.R(7)
.b(1);
Neat! This does, however, create a little more work for us behind the scenes:
// Struct for parameters - Named Parameter Idiom
struct SimParams {
private:
pop_t N0_; // Initial population size
pop_t Nmax_; // Maximum population size
year_t M0_; // Year when mutations stop occurring
year_t M1_; // Year when fishing begins
year_t M2_; // Year when fishing increases
rate_t fishing_rate_init_; // Initial fishing rate
rate_t fishing_rate_inc_; // Increase in fishing rate at year M2
gensize_t B_; // Genome size for Animals
gensize_t T_; // Threshold
gensize_t M_; // Mutation rate of the population
age_t R_; // Minimum age for reproduction
rate_t b_; // Birth rate
public:
friend class PopulationSim;
// Default constructor
SimParams() : N0_(0), Nmax_(0), M0_(0), M1_(0), M2_(0), fishing_rate_init_(0), fishing_rate_inc_(0),
B_(0), T_(0), M_(0), R_(0), b_(0) {};
inline SimParams& N0(pop_t val) { N0_ = val; return *this; }
inline SimParams& Nmax(pop_t val) { Nmax_ = val; return *this; }
inline SimParams& M0(year_t val) { M0_ = val; return *this; }
inline SimParams& M1(year_t val) { M1_ = val; return *this; }
inline SimParams& M2(year_t val) { M2_ = val; return *this; }
inline SimParams& fishing_rate_init(rate_t val) { fishing_rate_init_ = val; return *this; }
inline SimParams& fishing_rate_inc(rate_t val) { fishing_rate_inc_ = val; return *this; }
inline SimParams& B(gensize_t val) { B_ = val; return *this; }
inline SimParams& T(gensize_t val) { T_ = val; return *this; }
inline SimParams& M(gensize_t val) { M_ = val; return *this; }
inline SimParams& R(age_t val) { R_ = val; return *this; }
inline SimParams& b(rate_t val) { b_ = val; return *this; }
};
It is important to notice that because we made the members private, we need to declare PopulationSim
as a friend class
. The PopulationSim
constructor now looks a little different too:
PopulationSim::PopulationSim(const PopulationSim::SimParams & p) :
N0_(p.N0_), Nmax_(p.Nmax_),
M0_(p.M0_), M1_(p.M1_), M2_(p.M2_),
fishing_rate_init_(p.fishing_rate_init_), fishing_rate_inc_(p.fishing_rate_inc_),
B_(p.B_), T_(p.T_), M_(p.M_), R_(p.R_), b_(p.b_), cur_year_(0),
fishing_rate_(p.fishing_rate_init_) {
// ...
}
And that’s it! Until C++20 is available and support has spread (which could be a few years even after 2020), the Named Parameter Idiom is the best alternative to Named Parameters in C++.