Banner: ZumGuy Publications and Network

ZumGuy Publications and Network

C++: the Named Parameter Idiom

Posted by Sean on Friday, 17th November 2017 15:30
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++.

External links


You must be logged in to post messages.

Quote of the day...


ZumGuy Internet Promotions