Project

General

Profile


System details

Parameter declaration

In-class initialization

With C++11, it is possible to initialize class members in the the class definition:

struct A {
  int    i = 4;
  double j {12.3};
};

Such syntax is cleaner than having to specify an initialization list for the default constructor, which, in this case, is not necessary for the user to provide. We therefore encourage users to initialize the fhiclcpp parameters in the class definition. For example,

// PREFERRED
struct Config {
  Atom<int> param { Name("param"), 8 };
};

is equivalent to:

// DISCOURAGED
struct Config {

  Config() : param{ Name("param"), 8 } {}
  Atom<int> param;

};

The user should not have to interact with the configuration constructor at all--that is the job of Atom, or any of the fhiclcpp types. If you think you need to explicitly modify things using the Config constructor, please contact for guidance.

In-class sub-struct declaration

For a desired FHiCL configuration of:

pset : {

  pid : 13

  settings : {
    propagate : true
  }

}

the following configuration definitions are equivalent:

// OPTION 1
struct Settings {
  Atom<bool> propagate { Name("propagate"), true };
};

struct Config {
  Atom<int> pid { Name("pid") };
  Table<Settings> settings { Name("settings") };
};

or

// OPTION 2
struct Config {

  Atom<int> pid { Name("pid") };

  struct Settings {
    Atom<bool> propagate { Name("propagate"), true };
  };
  Table<Settings> settings { Name("settings") };
};

Which syntax is used is largely a matter of taste. Option 2 perhaps more closely reflects the FHiCL configuration syntax, but it can be more difficult to read.


Default values

fhiclcpp types that support default values

The Atom, Sequence and Tuple class each support the concept of a default value. Examples of fhiclcpp parameter declarations that receive default values are shown here. The Table class, however, can receive no default. To understand the motivation, consider the following mapping:

FHiCL category fhicl::ParameterSet fhiclcpp types
atom pset.get<bool>("flag", false) Atom<bool> flag { Name("flag"), false }
sequence pset.get<std::vector<int>>("dataset", std::vector<int>{1,2,4}) Sequence<int> dataset { Name("dataset"), {1,2,4} }
table pset.get<fhicl::ParameterSet>("config", fhicl::ParameterSet{} ) Table<Config> config { Name("config"), < ? default ? > }

A ParameterSet object is default-constructed to be empty, and it can grow entries as necessary. Such a concept does not exist, however, for a statically-typed Config object. The structure of Config is defined at compile-time and cannot be changed. For that reason, fhiclcpp Table objects cannot receive any default value. Of course, individual non-Table fhiclcpp members defined within the Config definition can receive default-value arguments.


Name and Comment

In each fhiclcpp parameter declaration, the fhicl::Name("name") argument is required--more specifically, the fhicl::Name declaration is required. Simply specifying the name as a string literal will yield a compile-time error:

struct Config {
  Atom<string> try1 {      "try1" ,         "Some comment" , "default_value" }; // Error!
  Atom<string> try2 { Name("try2"), Comment("Some comment"), "default_value" }; // Successful
};
The purpose for this restriction is two-fold:
  • By requiring Name's presence, the code explicitly states what is intended -- the fhiclcpp system will look for a FHiCL parameter with the name as specified by the string literal (the sequence of characters in between the quotation marks ""). As shown above, it is obvious that "default_value" is the default value, and not "try2" nor "Some comment". Same reason for providing the Comment declaration--the code is easier to understand. (N.B. The Comment argument is not required, but if a comment is desired, it must be specified using the Comment declaration.)
  • With C++14, it is not possible to generate a name from a variable name (i.e. "try2" produced as the name because the variable is try2) without using preprocessor macros. For future version of C++ (perhaps C++17 or C++21), some general reflection properties may be added so that such string-generation facilities are possible without the user of macros. By including the Name declaration, it will be much easier to parse the code base to eliminate such unnecessary declarations.

Bottomline: Use fhicl::Name and fhicl::Comment with string literals (i.e. something in between quotation marks "") as arguments. Trying to do something fancier introduces unnecessary complication and, likely, frustration.

Technical tidbits

This type of object declaration results in an rvalue. Loosely speaking, an lvalue is any variable that can appear on the left side of an assignment/declaration; an rvalue is the opposite. In this case, fhicl::Name("try1/2") and fhicl::Comment("Some comment") are temporary objects that are passed to the Atom<string> constructor. Since they are temporary, they have no validly accessible address and thus qualify as rvalues. There are restrictions on what you can do with rvalues. You do not need to know any of those details in order for the fhiclcpp parameter declarations to work as intended.


The '().' syntax

As mentioned before the syntax for accessing values of nested parameters looks like:

table1().table2().value()

where 'value' is the variable name of type Atom<int>.

It has been asked why the syntax is not:

table1.table2.value

which identically reflects the FHiCL syntax. The reason is that two features would need to be supported by C++ for this to work: overloading of the '.' operator, which is not allowed with C++14, and conversion of value to value(), which is supported under certain circumstances.

It would be possible to use an arrow '->' syntax, and, used with conversion, one could do:

int  value1 = table1->table2->value(); // 'int'
int  value2 = table1->table2->value;   // 'int'
auto value3 = table1->table2->value;   // 'Atom<int>' !

but notice that the automatically-deduced type for 'value3' is not what is expected. For the moment, we think the '().' syntax is not too burdensome, and the consistency in using the function call '()' for each fhiclcpp parameter, we argue, will lead to less confusion than that which can result from allowing type conversion as described above.


The '.get<>()' syntax for Tuple

If you are using the fhicl::Tuple<T...> fhiclcpp type, you will need to retrieve elements from the returned object using template arguments. A common usage pattern is:

// fhiclcpp parameter definitions
namespace {
  struct Config {
    Tuple<string,double> assoc { Name("assoc" ) };
  }
  Table<Config> config { Name("config") };
}

// usage
void f() 
{
  auto tuple = config().assoc(); // ==> std::tuple<string,double>

  // Get 'string' part of tuple 
  string const label_opt1 = std::get<0>(tuple);

  // Get 'double' part of tuple
  double const threshold_opt1 = std::get<1>(tuple);
}

If you would like to retrieve individual elements from the Tuple object, a 'get' function is provided so users do not need to go through the extra step of retrieving the 'std::tuple' object and then calling 'std::get' on it:

void g() 
{
  // Get 'string' part of tuple 
  string const label_opt1 = config().assoc.get<0>(); // N.B. no function call '()' on 'assoc' when 'get' is called

  // Get 'double' part of tuple
  double const threshold_opt1 = config().assoc.get<1>();
}

Why not '<>()'?

It would be desirable to do something like:

void h() 
{
  string const label = config().assoc<0>();  // Compile-time error!
}

and omit '.get' entirely. However, since the function that returns the value is an operator (i.e. the function call '()' operator), the C++ language requires that an individual element access overload of this operator would be called via:

void ugly() 
{
  string const label = config().assoc.operator()<0>();  // yuck!
}

Such a notation is clearly no improvement to '.get<>()'.