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
    // ...
    // 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 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()

Neat! This does, however, create a little more work for us behind the scenes:

    // Struct for parameters - Named Parameter Idiom
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
        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++.

You must be logged in to post messages.