DuneInstance

This module can be used to generate explicit template instantiations. Suppose you have a template test function that you want to call for a number of template arguments. You want to explicitly instantiate the function for each set of template arguments, and put the instanciation into its own translation unit. (This can be beneficial in that it limits the amount of code that the optimizer sees at once, and thus it can reduce both memory and cpu requirements during compilation.)

Examples

Let’s say you are writing a test mytest.cc and need to call a template function for several types:

#include <mytestsuite.hh>

int main() {
  MyTestSuite suite;

  suite.test<bool>();
  suite.test<char>();
  suite.test<int>();
  suite.test<double>();

  return suite.good() ? EXIT_SUCCESS : EXIT_FAILURE;
}

Let’s further say that you want to explicitly instantiate each used MyTestSuite::test() instance in it’s own translation unit. Then you need a series of files mytest_instance_bool.cc, mytest_instance_char.cc, etc, all with essentially the same content:

#include <mytestsuite.hh>

template void MyTestSuite::test<@TYPE@>();

where @TYPE@ is replaced by bool, char, etc as appropriate.

This is however not enough: all translation units need to know which instances of MyTestSuite::test() are instantiated explicitly so they do not instantiate them implicitly themselves (that would violate the one-definition rule). C++ only allows to declare individual instances as extern, not all of them collectively, so we need to put a list of all these instances into a header mytest.hh:

#include <mytestsuite.hh>

extern template void MyTestSuite::test<bool>();
extern template void MyTestSuite::test<char>();
extern template void MyTestSuite::test<int>();
extern template void MyTestSuite::test<double>();

We also need to include that header from each translation unit in the test, we can simply replace #include <mytestsuite.hh> with #include <mytest.hh>.

This is of course tedious and prone tp break if the list of tested types changes. To make this less fragile this module provides a series of commands: dune_instance_begin(), dune_instance_add(), and dune_instance_end(), which can be used to automatically generate the explicit instantiations for each type and the contents for the header and the body of main.

This may look like this in CMakeLists.txt:

dune_instance_begin(FILES mytest.cc mytest.hh)

foreach(TYPE IN ITEMS bool char int double)
  dune_instance_add(ID "${TYPE}" FILES mytest_instance.cc)
endforeach(TYPE IN ITEMS bool char int double)

dune_instance_end()

dune_list_filter(DUNE_INSTANCE_GENERATED INCLUDE REGEX [[\.cc$]])
dune_add_test(NAME mytest
  SOURCES ${DUNE_INSTANCE_GENERATED})

The call to dune_instance_begin() reads mytest.cc.in and mytest.hh.in and splits them into embedded templates and other content. It will replace occurrances of @VAR@ now in the other content and save the result for later.

The call to dune_instance_add() occurs in a loop. Each call will instanciate the embedded templates extracted earlier to replace occurance of @TYPE@ by the value of the variable TYPE set in the for loop. Then files containing explicit instantiatons will be generated as mytest_instance_bool.cc, mytest_instance_bool.cc, etc, from a template file mytest_instance.cc.in. The name of the generated files are the base file name from the template definition with the ID inserted before the extension. The name of the template file is the same base file name with .in appended.

dune_instance_end() is used to write mytest.cc and mytest.hh with the collected content from the embedded templates. The list of generated files will be available in the variable DUNE_INSTANCE_GENERATED.

The template files then look like this:

mytest.cc.in:

// @GENERATED_SOURCE@

#include <config.h>

#include <mytest.hh>

int main() {
  MyTestSuite suite;

#cmake @template@
  suite.test<@TYPE@>();
#cmake @endtemplate@

  return suite.good() ? EXIT_SUCCESS : EXIT_FAILURE;
}

mytest.hh.in:

// @GENERATED_SOURCE@

#include <mytestsuite.hh>

#cmake @template@
extern template void MyTestSuite::test<@TYPE@>();
#cmake @endtemplate@

mytest_instance.cc.in:

// @GENERATED_SOURCE@

#include <config.h>

#include <mytest.hh>

template void MyTestSuite::test<@TYPE@>();

The @GENERATED_SOURCE@ substitution is good practice, it tells a human reader that this file was generated and what the template file was, and it hints editors to go into read-only mode.

Embedded Templates

The template files given in dune_instance_begin() can contain embedded templates. These will be instantiated by dune_instance_add(), and all instantiations will be concatenated together and replace the original embedded template.

The begin of an embedded template is marked by a line containing @template@ or @template NAME@. Leaving off the name is equivalent to an empty name. dune_instance_add(TEMPLATE NAME) will only instanciate embedded templates whose name matches and ignore all others.

The end of an embedded template is marked by a line containing @endtemplate@ or @endtemplate NAME@. If a name is given, it must match the name of the embedded template it closes. If no name is given (or the name is empty), that check is omitted.

There may be arbitrary characters on the same line before or after the begin and end markers. These are ignored, so you can use them for comments or to trick your editor into proper indentation. The one exception is that the line surrounding the marker may not contain any @ characters to avoid ambiguities.

How Files And Strings Are Generated

The generation is done using the cmake command configure_file(...) for template files and string(CONFIGURE ...) for template strings. These simply substitute the current variable values, so make sure to set up the variables to substitute before calling dune_instance_add() or dune_instance_begin().

Refrain from using substitutions that begin with an underscore (i.e. @_my_local_var@). The generation functions in this module use such names for their local variables and may hide the variable you are trying to substitude.

When instantiating files we set up a few convenience variables before calling configure_file() that can be used in substitutions: @TEMPLATE@ contains the name of the template file. @INSTANCE@ contains the name of the file being generated, not including an implied ${CMAKE_CURRENT_BINARY_DIR}. Use @BINDIR_INSTANCE@ if you do want the implied ${CMAKE_CURRENT_BINARY_DIR}. @GENERATED_SOURCE@ contains a one-line message that this file was generated, including the name of the template file.

Main Interface

These are the ones you normally use.

Utilities

You would not use these directly under normal circumstances.