The Args Named Argument System

Miles Stoudenmire—Mar 26, 2016

The Problem

An unfortunate fact about C++ functions is that arguments must be passed in a fixed order. This is frustrating when you are ok with certain default arguments, but must provide them anyway to reach arguments further down the list.

To make matters worse, function arguments can be opaque and hard to interpret.
Consider the following code:

truncateMPS(psi,500,1E-5,false);

While it is clear that psi is some matrix product state to be truncated, what do the other parameters mean?

If the truncateMPS function accepted an Args object instead, we could call it like this:

truncateMPS(psi,{"Maxm=",500,"Cutoff=",1E-9,"ShowSizes=",false});

This is easier to read and lets us specify the parameters we care about, leaving the rest to have default values. For example, if we are happy with the default value for "Maxm" and "ShowSizes", we could call truncateMPS as

truncateMPS(psi,{"Cutoff=",1E-9});

The named arguments can be passed in any order we like

truncateMPS(psi,{"ShowSizes=",false,"Maxm=",500,"Cutoff=",1E-9});

Trailing equals signs "=" after each argument name are optional, and are ignored by the args system. So the following call would have identical results

truncateMPS(psi,{"ShowSizes",false,"Maxm",500,"Cutoff",1E-9});

Of course, in production code it is bad practice to "hard wire" numbers directly into functions; a better practice is to define all parameters one place, like at the top of main when reading from a parameter file. Even in this situation, the Args system makes things more readable and flexible regarding argument order and default arguments:

int maxm = 500;
Real cut = 1E-9;
bool show_sizes = false;
...
truncateMPS(psi,{"ShowSizes=",show_sizes,"Maxm=",maxm,"Cutoff=",cut});


Creating a Function that Accepts Args

To allow a function to take an Args object, first make sure the Args class is available

#include "itensor/util/args.h"

Or just do

#include "itensor/all.h"

Next define the last argument of your function to be

func(..., Args const& args = Args::global());

where the "..." means all the usual arguments the function "func" accepts. For example, the truncateMPS function above could be declared as

MPS truncateMPS(MPS const& psi, Args const& args = Args::global());

Making the default value Args::global() does the following: if no named arguments are passed then "args" will refer to the global Args object. By default the global Args object is empty; however, one can add arguments to Args::global() to set global defaults—for more details see the section on argument lookup order below.

Sometimes you may want to further modify the args object within your function. For such cases it is better to accept args by value

func(..., Args args = Args::global());


Accessing Named Arguments Within a Function

Using the fictitious truncateMPS function as an example, recall that it accepts three named arguments:

  • "Maxm" — an integer
  • "Cutoff" — a real number
  • "ShowSizes" — a boolean

(Named arguments can also be string valued.)

To access these arguments in the body of the function, do the following:

MPS
truncateMPS(MPS const& psi,
            Args const& args) //no Args::global() because we already
                              //specified it in the declaration
    {
    auto maxm = args.getInt("Maxm",5000);
    auto cutoff = args.getReal("Cutoff",1E-12);
    auto show_sizes = args.getBool("ShowSizes",false);

    ...

    }

The second argument to each of the above "get..." methods is the default value that will be used if that argument is not present in args. The order in which these functions are called is not important.

Occasionally a named argument should be mandatory. To make it so, just leave out the default value when calling getInt, getReal, getBool, or getString:

void
func(...
     Args const& args)
    {
    //Mandatory named argument
    auto result_name = args.getString("ResultName");
    ...
    }


Lookup Order and Global Args

When calling one of the "get..." methods (such as getInt or getString) on an Args instance, the value returned will be as follows:

  1. The value if defined in the instance itself

  2. If not defined in the instance, the value defined in the global Args object

  3. If not defined in the global Args, the default value provided to "get..."

Here is an example:

Args::global().add("DoPrint",true);

auto args = Args("Cutoff",1E-10);

auto do_print = args.getBool("DoPrint",false);
    // ^ do_print == true, found in Args::global()

auto cutoff = args.getReal("Cutoff",1E-5);
    // ^ cutoff == 1E-10, found in args

auto maxm = args.getInt("Maxm",5000);
   // ^ maxm == 5000, not found in args or Args::global() so default used


Creating Args Objects

There are a few different ways to construct Args objects. The simplest is the Args constructor, which accepts any number of name-value pairs:

auto args = Args("Name=","some_string",
                 "Size=",100,
                 "Threshold=",1E-12,
                 "DoThing=",false);

The above constructor is what is called when calling a function using the syntax

someFunc(...,{"Name=","some_string","Size=",100});

Args objects can also be constructed from strings of the form "Name1=value1,Name2=value2,...". So for example

auto args = Args("Name=some_string,Size=200,Threshold=1E-10");

Finally, arguments can be added to an Args object that is already defined using the add method. Adding an argument that is already defined overwrites the previously defined value.

auto args = Args("Name=","name1");

args.add("Threshold=",1E-8);

if(args.defined("Threshold")) println("args contains Threshold");

Back to Tutorials
Back to Main