User Guide
Generator

Generator

Before reading this chapter, please make sure you have read the Checker chapter and are familiar with the basic usage of cplib::var::Var. This chapter will not repeat the introduction to this part.

The generator is used to generate a input file according to the requirements of the command-line parameters and output to standard output (stdout).

In most cases, only the command-line parameters given at the time of invocation can determine the generation result of the generator. In other words, the generator does not need to process any input streams or input files.

The relevant code for this section can be found in the GitHub repository (opens in a new tab).

Let's start with a simple example to explain how to write and use the generator.

Requirements: Implement an input file generator for the integer A+B problem, called with the command ./gen [--same] --n-max=<value> --n-min=<value>. The n-min and n-max specify the range of integers aa and bb. The same flag indicates whether to set a=ba = b.

Implementation

Here is a reference implementation of a generator:

gen.cpp
#include "cplib.hpp"
 
using namespace cplib;
 
CPLIB_REGISTER_GENERATOR(gen, args,
                         n_min = Var<var::i32>("n-min", -1000, 1000),
                         n_max = Var<var::i32>("n-max", -1000, 1000),
                         same = Flag("same"));
 
void generator_main() {
  using namespace args;
 
  if (n_min > n_max) panic("n_min must be <= n_max");
 
  int a = gen.rnd.next(n_min, n_max);
  int b = same ? a : gen.rnd.next(n_min, n_max);
 
  std::cout << a << ' ' << b << '\n';
 
  gen.quit_ok();
}

The main difference between the generator and other CPLib work mode is reflected in the CPLIB_REGISTER_GENERATOR macro. This macro has at least 2 parameters, in order:

  • The 1st parameter is the name of the global variable representing the generator state. This parameter has the same role as the first parameter of the registration macro in other work modes.
  • The 2nd parameter is the name of the namespace bound to the command-line arguments.
  • The 3rd to the last parameters (if exists) are the parsers of command-line arguments. The format is parser_name = ParserType(constructor_arguments).

Here, args is set as the name of the namespace that binds to command line arguments. This will export a namespace named args in the global namespace, and store the results of parser into variables in this namespace. The variable name of each parser's parsing result is the same as parser_name.

Parsers will automatically parsing the command line arguments during initialization. There are two parser types currently supported:

  • Flag: A parser for "flag-style" (--var) arguments. The constructor of Flag takes a std::string as the name of the command line argument (i.e., the var part in --var, excluding the preceding two dashes). When running this parser, it checks if --var exists in the command line arguments. If it does, it sets the member variable value to true; otherwise, it sets it to false.

  • Var<T>: A parser for "variable-style" (--var=<value>) arguments, where T is the type of a variable template (which needs to be derived from the cplib::var::Var). The constructor of the Var<T> type takes an instance of type T, and the return value of its name() method serves as the name of the command line argument (i.e., the var part in --var=<value>, excluding the preceding two dashes). When running this parser, it calls the read_from function of type T to parse the <value> string, and stores the parsing result in the value member variable.

The Var<T> and Flag structures are defined in a hidden namespace, so there is no need to worry about global namespace pollution.

Usage

The usage of the generator is similar to that of the checker. The only difference is that instead of passing file name arguments, you need to pass command-line arguments according to the definition of the args namespace.

Suggestions

In summary, when writing more complex generators, we recommend the following suggestions:

  • The generator needs to ensure that the generated result is only related to the command-line argument, so do not use functions like std::time or std::clock that may return different results each time they are called.
  • Do not use std::rand or other custom generators. Instead, use val.rnd.
  • Do not attempt to manually set the random number seed for val.rnd. Use the seed automatically generated in the initializer based on the command-line arguments.
  • You can choose any way to output the data to standard output. However, to ensure the efficiency of std::cout, CPLib automatically disables the synchronization of C++ streams and stdio in generator mode, so do not mix C-style output (std::printf, etc.) and C++-style output (std::cout, etc.).
  • If the data generation methods of multiple generator programs are roughly the same, you should merge them into one generator program and differentiate them using command-line arguments.