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>
. Then-min
andn-max
specify the range of integers and . Thesame
flag indicates whether to set .
Implementation
Here is a reference implementation of a generator:
#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 ofFlag
takes astd::string
as the name of the command-line argument (i.e., thevar
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 variablevalue
totrue
; otherwise, it sets it tofalse
. -
Var<T>
: A parser for "variable-style" (--var=<value>
) arguments, whereT
is the type of a variable template (which needs to be derived from thecplib::var::Var
). The constructor of theVar<T>
type takes an instance of typeT
, and the return value of itsname()
method serves as the name of the command-line argument (i.e., thevar
part in--var=<value>
, excluding the preceding two dashes). When running this parser, it calls theread_from
function of typeT
to parse the<value>
string, and stores the parsing result in thevalue
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
orstd::clock
that may return different results each time they are called. - Do not use
std::rand
or other custom generators. Instead, useval.rnd
. - Do not attempt to manually set the random number seed for
val.rnd
. Use the seed automatically generated in theinitializer
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.