/home/adeel

Using variadic templates with lambda expressions in C++ for constrained optimization

Constrained optimization problems are encountered in numerous domains, such as protein folding, Magnetic Resonance Image reconstruction, and radiation therapy. In this problem, we are given with an objective function which is to be minimized or maximized with respect to constraints on some variables. The constraints can either be soft constraints or hard constraints, which can be specified by boolean operators, such as equality, relational, and conditional operators.

This post provides insight on how to model constraints using lambda expressions, and how to pass a varying number of constraints to a function using variadic templates. Before moving on with the C++ implementation, it will be helpful to review how variadic functions are used in C and how they differ from the current standard.

C-style variadic functions

Support for variadic macros was introduced in C as part of the C99 standard. This allowed users to define functions that could take a varying number of arguments. A common example of this is the printf library function, which can be used to print an arbitrary number of values.

The cstdarg header file defines the necessary macros to create variadic functions. The syntax requires specifying the function name followed by its arguments. The last argument is always the ellipsis ... operator. An example of this is shown below:

void print_arguments(int nargs, ...) { // (1)
  va_list vl;                          // (2)
  va_start(vl, nargs);                 // (3)

  for (int i = 0; i < nargs; ++i)
    printf("%s", va_arg(vl, char*));   // (4)

  va_end(vl);                          // (5)
}

Calling the above function as print_arguments(5, "h", "e", "l", "l", "o") will produce the output hello. Let’s go over the function definition step-by-step:

  1. nargs is the first argument which, in this case, specifies the argument count this function should expect. The ellipsis ... operator specifies an arbitrary number of arguments.
  2. An object of va_list is created to hold information about the variable arguments.
  3. The variable argument list vl is initialized using the va_start macro. Here nargs specifies the number of arguments.
  4. va_arg retrieves the next argument. Each call to this macro modifies the state of vl to point to the next element. Notice that we had to explicitly pass the function type, so this function is not type agnostic.
  5. The va_end macro should be invoked before the function returns. It performs cleanup of the va_list object so that it’s no longer usable.

Drawbacks

There are several drawbacks using the above function. Firstly, we have to specify the number of arguments as part of the function call, although, this is not necessary, as we could also pass a token specifying argument end, such as the NULL operator. The printf function does this by looking at the formatting specifiers (%s in this case). This is why it fails if the format string does not match the argument list.

Another major drawback is that we have to know, in advance, the type of data we’re passing. Passing an incorrect type to va_arg can result in a segmentation fault. This makes it cumbersome to process data with varying types. Finally, the types are evaluated at run-time, which can be a performance overhead.

C-style variadic functions are discouraged in C++ as they are not type safe, apart from other issues that are mentioned above. In C++, we have the ability to define variadic templates, which are strongly typed. These are discussed in the following section.

Variadic templates in C++

Variadic templates became part of the C++ standard in August 2011, commonly known as C++11. They allow a way of writing functions that take a varying number of arguments in a type safe way using pack expansion. Another advantage they have over the traditional variadic functions is that all argument handling logic is performed at compile-time, which leads to better performance.

The example below defines a variadic function template which returns the number of arguments passed to it:

template<typename... Types>        // (1)
std::size_t nargs(Types... args) { // (2)
  return sizeof... (args);         // (3)
}

Calling nargs(1, "2", 3.5) will return 3. The function details are described below:

  1. We first specify a variadic template using typename... Types, the contents of which are called parameter packs.
  2. The parameters packs are unpacked inside the function header. So, in the above case, calling nargs(1, "2", 3.5) will generate:
  3. template<int, const char*, double>
    std::size_t nargs(int param1, const char* param2, double param3);
    
  4. The sizeof… operator queries the number of arguments in a parameter pack.