C++ Type Generator
What is C++ Type Generator?
Hello World!
Generator usage and options
Type description commands
Controlled conversions between unrelated types
Syntax issues
Performance of the generated code
Download typegen:
![]() |
typegen-1.1.0.tar.gz | (7 kB) |
![]() |
typegen-1.1.0.zip | (8 kB) |
See changelog for the list of improvements.
The C++ Type Generator is a very simple program generating classes that
can play the role of value types that have characteristics according to
type descriptions provided by the user.
The purpose of the generator is to promote good software engineering
practice by making it much easier to think in terms of domains and types with constraints
and by
providing extensive assistance
to the programmer in terms of finding bugs related to unsafe usage of
fundamental types in C++.
The assistive abilities of the generated types include:
- separation of domains that are logically separate even though they use the same underlying type
- handling of ranges and different constraint violation policies
- fine-grained control of conversions between types
- definition of allowed operations
- automatic detection of uninitialized values
- ... and more
In other words, the C++ Type Generator is supposed to help programmers
achieve high level of type safety in their C++ programs.
It is supposed to compete with approaches based on templates and
metaprogramming in the following ways:
- Clear and straightforward
compiler diagnostics. Since the generator creates dedicated
classes instead of
typedefs to some convoluted template instantiations, the compiler diagnostics clearly direct the user to the actual problem, without the need to dig through tens or even hundreds of lines of unrelated diagnostic noise. - Fast compilations.
The
generator creates definitions that are minimal and self-contained with
respect to what is to be achieved. This means that the generated code
does not rely on any external library, which in the case of
template-based approach is usually entirely placed in header files.
This is especially important for types which are widely used in the
project, because they tend to be directly or indirectly
#included everywhere, thus significantly influencing the overall compilation time. - Performance. The generated code is very simple, which promotes aggressive optimizations.
- Portability. Because the generated code is straightforward and simple in terms of language features that are in use, it should be also easy to compile by older compilers.
- Simplicity of underlying mechanisms. The generated code uses basic language features and as such has more chances of acceptance in code reviews and external audits.
The generator itself is a simple script that works by interpreting the type definition file provided by user and creating the C++ header file with definitions of new types.
# the following is in mytypes.tcl:type string Greetingstype number Distancetype number Velocitytype number Durationoperation Velocity * Duration -> Distance// and the following is in hello.cpp:#include "mytypes.h"#include <iostream>using namespace std;int main(){ Greetings msg = "Hello!"; cout << msg << '\n'; cout << "Enter velocity: "; Velocity v; cin >> v; cout << "Enter duration: "; Duration t; cin >> t; Distance d = v * t; cout << "The distance is: "
<< d << '\n';}
The whole project can be compiled in the following way:
$ tclsh typegen.tcl mytypes.tcl$ g++ hello.cpp
The type-safety gain in this code is based on the fact that the defined types are really distinct from each other and there is only one operation that allows to perform conversions between the three numeric types.
The generator itself is implemented in the typegen.tcl
file, which is
a Tcl script. It can be invoked as:
$ tclsh typegen.tcl definition_file_name [option value option value ...]
The definition_file_name is a name of the file that
contains definitions of types provided by user.
The generated file has the same root name as the definition file, but
with the .h suffix.
All options are boolean variables and their values need to be provided
after each option name.
Currently the following
options are recognized:
| option | meaning |
|---|---|
-rawBaseTypes |
If the value is true,
the generator will not generate class wrappers for defined types, but
only simple typedefs that create aliases to the
underlying
types. This setting basically switches off all the type safety that
could be otherwise gained, but might be useful for example for final
"release" builds, where the correctness of the program was already
verified and the possible gain in performance justifies the loss of
safety belts.
Note: apart from removing all safety belts, this option can influence the semantics of types with wrapped or saturated constraints. Use it with care.
The default value for this option is $ tclsh typegen.tcl mytypes.tcl -rawBaseTypes true
|
-withTypeInfo
|
If the value is true,
the generator will generate for each number type the additional class
named TypeName_type
which contains the typedef
for the base type of the given type with additional begin()
and end() static member functions if the type has range
constraint.
The default value for this option is $ tclsh typegen.tcl mytypes.tcl -withTypeInfo false
|
-checkUninitialized |
If the value is true,
the generated types assert that the value is already initialized before
being read.The default value for this option is true.
|
-includeString |
If the value is true,
the generated header file contains the #include <string>
directive. Setting it to false makes sense only when the
string types are not used at all or they use something different than std::string
as their base type.The default value for this option is true.
|
-includeStdExcept |
If the value is true,
the generated header file contains the #include <stdexcept>
directive. Setting it to false makes sense when none of
the types uses exceptions for reporting constraint violation conditions.The default value for this option is true.
|
-withIOStreams |
If the value is true,
the generated header file contains the #include <istream>
and #include <ostream> directives and the types
have insertions and extractions operations for standard streams.
Setting this option to false will suppress the generation
of these operations.The default value for this option is true.
|
-withDomainCast |
If the value is true,
the generated header file contains the domain_cast
template function provided specifically for explicit conversions
between generated types.The default value for this option is true.
|
-withFastSubtypes |
If the value is true,
the implicit conversions from the subtype to its supertype bypasses the
range checking of the supertype.The default value for this option is true.
|
The following commands are provided for type generation and can be used in the type definition file:
| command | meaning | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
literal |
This is the basic command that
forces the generator to inject the given string in its output.
Example: literal "\#include \"MyBigNumber.h\""
|
||||||||||||||||||||||||
include |
This command is a helper
for inserting #include directives.
Example: include MyBigNumber.hinclude <some/system/header.h>
|
||||||||||||||||||||||||
type number |
This command defines new numeric
type. In its simplest form it takes one argument, which is the name of
the new type.
Example: type number MyNumber
Possible additional options (always after the name of the type) are:
For example, with all options having their default values, the result of this definition: type number MyType -range {0 100}
is the following code in the generated header file (some definitions and bodies are not shown here): class MyType_type;class MyType{public: typedef MyType_type info_type; typedef int base_type; MyType(base_type v); MyType(); MyType(MyType const &
other); void operator=(MyType const
&
other); void operator=(base_type v); base_type get_value() const; bool operator==(MyType const
&
other)
const; bool operator!=(MyType const
&
other)
const;private: base_type value_;};class MyType_type{public: typedef int base_type; static MyType begin() {
return
MyType(0); } static MyType
end() {
return
MyType(100); }};std::ostream & operator<<(std::ostream
& os,
MyType const
& v);std::istream & operator>>(std::istream
& is,
MyType &
v);
|
||||||||||||||||||||||||
type string |
This command defines new string
type. In its simplest form it takes one argument, which is the name of
the new type.
Example: type string MyString
Possible additional options (always after the name of the type) are:
For example, with all options having their default values, the result of this definition: type string MyString
is the following code in the generated header file (some definitions and bodies are not shown here): class MyString{public: typedef std::string
base_type; base_type value; MyString(char const * str); MyString(base_type const
& v); MyString(); MyString(MyString const
&
other); void operator=(MyString
const
& other); void operator=(base_type
const
& v); void operator=(char const *
str); bool operator==(MyString
const
& other)
const; bool operator!=(MyString
const
& other)
const; base_type const &
get_value()
const;};std::ostream & operator<<(std::ostream
& os,
MyString
const & v);std::istream & operator>>(std::istream
& is,
MyString
& v);
Note that in this class there is a public member |
||||||||||||||||||||||||
operation |
This command defines the binary
operation that relates three types, where at least one of the arguments
is a generated type. Any of the binary operators is allowed, if it is supported by the underlying types of the given types. Example: operation Velocity * Duration -> Distance
The result of the above command is the following generated code: inline Distance operator*(Velocity const & left, Duration const & right){ return left.get_value() * right.get_value();}
If neither of the arguments is a generated type, then this command is ignored. |
||||||||||||||||||||||||
insideNamespace |
This command allows to define
types within regular C++ namespaces, but unlike the literal
"namespace ..." command, it provides the notion of the namespace
to the generator, allowing proper generation of other related code.Namespaces can be nested or re-opened. Example: insideNamespace MyNamespace { type number MyNumber}
As a result, the insideNamespace N1 { type number U insideNamespace N2 { type number V }}insideNamespace N3{ type number X -subtypeOf N1::N2::V insideNamespace N4 { type number Y -subtypeOf N1::U }}
|
Controlled conversions between unrelated types
In order to allow controlled conversions between unrelated types, the domain_cast
operation is provided, which syntax is the same as for other C++ casts.
If the source type is not generated, the domain_cast is
equivalent to static_cast.
Example:
With the following type definitions:
type number Atype number B -subtypeOf Atype number C
the following C++ code is legal:
A a = ...;B b = ...;C c = ...;// B is a subtype of A, so this operation is legal implicitly:a = b;// this is not necessary, but still possible:a = domain_cast<A>(b);// and the other way round and with unrelated types it is always:b = domain_cast<B>(a);a = domain_cast<A>(c);
Hint: the domain_cast is as ugly as other C++ casts and
is therefore easy to find in source code.
The whole "type definition language" is implemented in Tcl and is
nothing more than a set of Tcl commands - and the type definition file
is nothing more than a regular Tcl program.
Those programmers who already know Tcl are less likely to run into
problems with Tcl syntax (notably some quoting issues, things related
to line breaking and curly braces and so on) and in addition can enjoy
other Tcl features in their type definitions, like:
set PI 3.1415926type number Angle -use double -range [list -$PI $PI]type number MyRange -range [getTheRangeFromExternalDatabase]
or anything else (like source'ing other files, etc.) that
they find useful.
For those programmers who don't know Tcl the only reasonable recommendation is: Learn it. :-)
Performance of the generated code
For performance reasons, all functions are generated as inline. Modern
compilers should have no problems optimizing them to the level that is
equivalent to the use of fundamental types directly.
If the aggressive optimization options cannot be used and the ultimate
performance is a goal, the following hints may prove useful with regard
to the numeric types:
- Use the global option
-checkUninitializedto switch off checking for uninitialized values. This feature costs you additionalboolin each type and theassertwith every read. - Use the global option
-rawBaseTypesor its local override-rawBaseTypeto force the generator to produce plaintypedefs to the relevant base types.
Note: Both options above remove the safety belts which undermines the
whole idea of this code generator. The use of these options is
justified only after the programmer had already gained the confidence
that the code is correct and well tested. Compiling and testing the
program with and without these options (kind of debug/release builds)
might be a justified strategy, but it is to be reminded that wrapping
and saturation for numeric types will obviously lose their semantics
when the type is just typedefed to the underlying base
type.
