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:
The value if defined in the instance itself
If not defined in the instance, the value defined in the global
Args
objectIf 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