Controlled Vars

To Never Forget Initializing Your C++ Variables

Controlled VariablesThe controlled_vars.h and other header files are C++ templates, that gives you the ability to control the initialization of your basic C types (i.e. char, int, long long, etc.)

Especially, it allows you to avoid forgetting the initialization of variable members since these variables cannot be initialized where declared1. Classes are here to help you and they do a great job at this, even for basic types such as char, int, and long variables. Really! My main C++ bugs came from forgetting the initialization of variable members and this is the reason why I created these headers.

Controlled Variables can actually be used anywhere you declare a basic type variable without immediately initializing it (and limited controlled variables anywhere a bounded value is used.)

 

Requirements

To compile the generator of the Controlled Variables header files, I use CMake and a C++ compiler (also the generator is nearly all written in C, it has a tad bit of C++ such as inline variable declarations.)

If you don't want to regenerate the header files, all you need is your standard C++ compiler and the controlled_vars_headers-x.y.z.tar.gz package.

Debian Packages

We now generate Debian packages for Ubuntu 12.04, 12.10, 13.04, 13.10, 14.04. See the Snap! Project page for details on how you install a PPA and install new packages from launchpad (packages).

Download

You can find the source and header files on SourceForge.net.

Filename2 Contents
controlled_vars-x.y.z.tar.gz Source of the Controlled Variables generator
controlled_vars_headers-x.y.z.tar.gz The generated headers (to avoid recompiling)

Where x.y.z represents the major (x), minor (y) and patch (z) versions.

We also offer ready to install packages on LaunchPad: Snap CPP (for Ubuntu users.)

Issues

Please, if you find bugs or have problems using the controlled_vars.h header file or its generator, post your concerns on SourceForge.org.

Projects Using this one

The Controlled Variables headers are a dependency of the libQtCassandra library.

Using Boost static_assert.hpp

We are re-using the trick defined in the static_assert.hpp file from Boost in order to verify that the limited variables have valid bounds and when an initialization value is defined, that it is within the bounds.

In our code, it is renamed using controlled variables naming convention so you may still use boost as usual. However, we did not want to add a dependency on boost to use the controlled variables so we redefined this macro.

Note that at this time we limited the definition to g++ and cl (Microsoft compiler.) Other compilers can be added with time although all you need to do really is switch compiler to run the compilation tests. Plus the assertion is nice but not required since it will anyway throw an exception at run time whenever a variable is instantiated.

Programmer Documentation

The Controlled Variables header file includes a set of classes and typedef's that help you create safer code (since your variables get initialized, and if not, you get a throw--at least in DEBUG mode.)

The following documents the Controlled Variables classes.

Note: The documentation is not inline since (1) the classes are generated and (2) the code is extremely repetitive.

Library Version

Since version 1.3.0, the Controlled Variables include a version file that can be used to determine the version at compile time or at run-time.

The library defines three macros as follow:

#define CONTROLLED_VARS_MAJOR 1
#define CONTROLLED_VARS_MINOR 3
#define CONTROLLED_VARS_PATCH 0

The macros can be used as static values at compile time.

The library also defines the following functions:

int controlled_vars_major()
int controlled_vars_minor()
int controlled_vars_patch()
const char *controlled_vars_version()

The functions returning an integer return one of the version numbers as defined by the macros.

The controlled_vars_version() returns the version in the form of a string such as "1.3.0".

Debug Mode (CONTROLLED_VARS_DEBUG and CONTROLLED_VARS_LIMITED)

Some of the classes make use of a debug version (mainly the no init templates and the limited classes, see below.) In order to benefit from the debug code, you need to use the -D command line option of your C++ compiler or use a pre-processor statement like this:

#define CONTROLLED_VARS_DEBUG 1
#define CONTROLLED_VARS_LIMITED 1

Note that the variables just need to be defined.

The debug mode adds code the verifies that your variables are properly initialized (no init templates) or that the value remains within the specified boundaries (limited templates.) When an error is detected, the code raises an exception (see exceptions below.)

To Be Honest

I call this the Debug Mode but it can be used against a release version since it does not check any specific debug flag such as _DEBUG. This actually means you could create 3 versions: a debug version, a release version with controlled variables checks still turned on, and a final release version with the controlled variables checks all turned off.

Note that in debug mode all the types defined in the header include a function named is_initialized() that returns true when the variable was initialized.

bool is_initialized() const;

You can use this function in your own debug code to check whether a variable was initialized or not. Don't forget to test the CONTROLLED_VARS_DEBUG whenever you want to use this function:

  bool was_init = true;
#ifdef CONTROLLED_VARS_DEBUG
  was_init = my_var.is_initialized();
#endif
  if(!was_init) std::cerr << "We've got a problem!" << std::endl;

In this example you see that by default I set the was_init variable to true since by default you expect an initialized variable.

The CONTROLLED_VARS_LIMITED does not generate such side effects.

Namespace (namespace controlled_vars)

The Controlled Variables are defined in a namespace named controlled_vars.

As usual, you can either use a using command or specify the namespace each time you reference a type defined in the Controlled Variables header.

typedef controlled_vars::auto_init<int32_t, 100> hundred_number_t;

or

using controlled_vars;
typedef auto_init<int32_t, 100> hundred_number_t;

Either way works. We have chosen names that shouldn't clash with anything, but of course... and that's why we include a namespace.

Controlled Variables Exceptions (class controlled_vars_error)

The Controlled Variables generate exceptions whenever an error is detected.

The Controlled Variables exceptions derive from std::logic_error since it is considered invalid (i.e. none of the exceptions defined in the Controlled Variables header should ever be raised. Actually, without the CONTROLLED_VARS_DEBUG flag, most of the throw statements are removed!)

The constructor of the Controlled Variables exception classes require a string message describing the reason for the exception. The message gives information why the exception is being raised so it gets really easy to fix the discovered bug.

There are 3 exceptions, the base type:

  • controlled_vars_error

You can catch using the base type in which case you will catch all the Controlled Variables exceptions at once. However, the classes never directly raise that exception.

And two derived types that are raised in specific circumstances:

  • controlled_vars_error_not_initialized

The variable is being read before it was ever initialized.

  • controlled_vars_error_out_of_bounds

An attempt to set a limited variable to a value too small or too large.

Note that invalid bounds in a limited controlled variable is found at compile time when you use GNU C++ or MSC as a compiler. Therefore this error would really only occur when you set a variable.

Type of T in the template<T> classes

All the classes defined below include a typedef that is used to create a type named primary_type_t. The declaration goes like this:

typedef T primary_type_t;

The primary_type_t can be used in situation where the type itself must be used. To define a variable of the same type as a zuint16_t, one should use:

zuint16_t::primary_type_t

This way you avoid surprises.

Classes functions (ptr(), value(), is_initialized() operators)

All the type classes define a small set of functions as follow:

T *ptr() and const T *ptr() const

Returns a direct pointer to the variable member.

This is not safe but at times it is necessary to directly access the variable (i.e. when you need to pass it as a reference to a sub-routine.)

Obviously, the non-const version let you modify the value without any protection (see the talk in the limited type below.)

T value() const;

Retrieve the current value of your variable as the template defined type. So if you use a template class with type int32_t, then value() returns an int32_t. There are times when the compiler will not be able to choose the correct casting operator automatically. Using this function is a quick way to cast the value to its primary type.

In the following, the last two statements are equivalents:

typedef controlled_vars::auto_init<int32_t, 123> my_type_t;
my_type_t number; // value is 123
std::cout << static_cast<my_type_t::primary_type_t>(number);
std::cout << number.value();
bool is_initialized() const;

This function is described in the chapter about the Debug Mode.

It is only defined when CONTROLLED_VARS_DEBUG is defined (i.e. in Debug Mode) and returns true if the value of the variable was already defined, and false otherwise. Only the no_init::is_initialized() function may return false since the other two templates either have a default initialization or force you to initialize your variables.

[T/bool/class&] operator ... () [const];

The classes define all the operators that they need. This means pretty much all the operators are overloaded (except the parenthesis operator.)

To simulate a standard integer or floating point type, you want to have all the possible operators (+, -, *, /, etc.) redefined and each with all the basic C++ types (char, short, int, etc.) This is important so the compiler knows how to apply all the operations properly.

Only defining operators for the same type generates problems when you attempt to use floating points with integers and vice versa. Only defining a small set of types generates problems when not using one of those types.

Note that does not preclude you from getting warnings when you do operations such as a = b where a is a smaller type than b and a loss of data may happen. However, it makes for a very large header file.

I do not list all the supported operators here since it's all the C++ operators. You should already know them. In any events, it means you can use variables of these types like standard C++ types.

Auto-Initialized Variables (template<T> class auto_init)

The first type of variables are auto-initialized variables. These variables do not require you to initialize them at all. Defining a variable of this type without a constructor simply sets the value to its default value.

A set of default types are defined with the default value of zero since that's quite often what you need. These are listed here:

typedef auto_init<bool>              zbool_t;
typedef auto_init<char>              zchar_t;
typedef auto_init<signed char>       zschar_t;
typedef auto_init<unsigned char>     zuchar_t;
typedef auto_init<wchar_t>           zwchar_t;
typedef auto_init<int16_t>           zint16_t;
typedef auto_init<uint16_t>          zuint16_t;
typedef auto_init<int32_t>           zint32_t;
typedef auto_init<uint32_t>          zuint32_t;
typedef auto_init<long>              zplain_long_t;
typedef auto_init<unsigned long>     zplain_ulong_t;
typedef auto_init<int64_t>           zint64_t;
typedef auto_init<uint64_t>          zuint64_t;
typedef auto_init<size_t>            zsize_t;
typedef auto_init<time_t>            ztime_t;

typedef auto_init<bool, false>       fbool_t;
typedef auto_init<bool, true>        tbool_t;

typedef ptr_auto_init<bool>          zpbool_t;
typedef ptr_auto_init<char>          zpchar_t;
typedef ptr_auto_init<signed char>   zpschar_t;
typedef ptr_auto_init<unsigned char> zpuchar_t;
typedef ptr_auto_init<wchar_t>       zpwchar_t;
typedef ptr_auto_init<int16_t>       zpint16_t;
typedef ptr_auto_init<uint16_t>      zpuint16_t;
typedef ptr_auto_init<int32_t>       zpint32_t;
typedef ptr_auto_init<uint32_t>      zpuint32_t;
typedef ptr_auto_init<long>          zpplain_long_t;
typedef ptr_auto_init<unsigned long> zpplain_ulong_t;
typedef ptr_auto_init<int64_t>       zpint64_t;
typedef ptr_auto_init<uint64_t>      zpuint64_t;
typedef ptr_auto_init<size_t>        zpsize_t;
typedef ptr_auto_init<time_t>        zptime_t;

Note that all of these types are not available on all platforms.

For example, the zbool_t creates a Boolean variable that's initialized to false. And the zint32_t creates an integer initialized to zero.

zint64_t v; // equivalent to int64_t v(0);

We offer two specializations that are fbool_t and tbool_t. The first is equivalent to zbool_t and the second initializes the boolean variable to true.

When you need a variable to often be initialized to another value such as 1, you can create your own variable type as follow:

typedef auto_init<int32_t, 1> one_int32_t;
one_int32_t v; // equivalent to int32_t v(1);

While writing code in a function, controlled variables may not be useful. However, imagine you are writing variable members in a C++ class. Now you do not need to think about initializing them.

class MyThread {
    zbool_t   f_working; // whether the thread is working
public:
    MyThread() {} // no need to init f_working!
};

The default value is defined in the class as DEFAULT_VALUE. So for example a variable declared as a zbool_t type can find the default value like this:

init(zbool_t::DEFAULT_VALUE);
zbool_t b;
init(b.DEFAULT_VALUE); // it is a public variable member.

Obviously, you know what the default value of "z" variables are. It's always zero. Other types, however, could have other default values.

For pointers, setting a default is a little more complicated as it requires a trait. This is because template parameter initialization only supports constant integers. This is valid for floating points as well and I'll fix that at some point.

A trait is a class that has to define a list of pre-defined types, functions, variable members... In our case, we only defined one function named DEFAULT_VALUE(). This function is called to initialize the variable on construction when the default constructor is used and when the reset() function is used without any parameters.

The trait is a class template. To make it work with your pointer, you want to specialize the template setting the type T to your pointer type (not the pointer, but its type only.)

An interesting side effect is the use of safe pointers with Singletons. The trait can be written to call the GetInstance() function of your singleton. Now you have a pointer to that singleton that auto-initializes itself.

Mandatory Initialization (template<T> class need_init)

At times, you create a type that you want the user to initialized every single time, no matter what. This is useful for types that do not really have a default value per se.

This class does not have a default constructor (i.e. a constructor that does not take any parameter.) This forces you to initialize it wherever you use it. This includes you variable members. By default, a basic C++ type is simply not initialized. A Controlled Variable of mandatory type generate an error instead.

class MyClass {
    int random_value;
    mint32_t forced_value;
public:
    MyClass() : forced_value(3) {}
};

In this example, the class has two members. With the forced_value(3) on the constructor, the class will compiled, remove it and you get an error that there isn't a default constructor.

However, whether random_value is defined in the constructor list is not a problem, it will compile. This means the random_value member is going to be any value that was at that location at the time you created your class. Probably not what you want...

typedef need_init<bool>              mbool_t;
typedef need_init<char>              mchar_t;
typedef need_init<signed char>       mschar_t;
typedef need_init<unsigned char>     muchar_t;
typedef need_init<int16_t>           mint16_t;
typedef need_init<uint16_t>          muint16_t;
typedef need_init<int32_t>           mint32_t;
typedef need_init<uint32_t>          muint32_t;
typedef need_init<long>              mplain_long_t;
typedef need_init<unsigned long>     mplain_ulong_t;
typedef need_init<int64_t>           mint64_t;
typedef need_init<uint64_t>          muint64_t;
typedef need_init<float>             mfloat_t;
typedef need_init<double>            mdouble_t;
typedef need_init<long double>       mlongdouble_t;
typedef need_init<size_t>            msize_t;
typedef need_init<time_t>            mtime_t;

typedef ptr_need_init<bool>          mpbool_t;
typedef ptr_need_init<char>          mpchar_t;
typedef ptr_need_init<signed char>   mpschar_t;
typedef ptr_need_init<unsigned char> mpuchar_t;
typedef ptr_need_init<wchar_t>       mpwchar_t;
typedef ptr_need_init<int16_t>       mpint16_t;
typedef ptr_need_init<uint16_t>      mpuint16_t;
typedef ptr_need_init<int32_t>       mpint32_t;
typedef ptr_need_init<uint32_t>      mpuint32_t;
typedef ptr_need_init<long>          mpplain_long_t;
typedef ptr_need_init<unsigned long> mpplain_ulong_t;
typedef ptr_need_init<int64_t>       mpint64_t;
typedef ptr_need_init<uint64_t>      mpuint64_t;
typedef ptr_need_init<float>         mpfloat_t;
typedef ptr_need_init<double>        mpdouble_t;
typedef ptr_need_init<long double>   mplongdouble_t;
typedef ptr_need_init<size_t>        mpsize_t;
typedef ptr_need_init<time_t>        mptime_t;

Note that all of these types are not available on all platforms.

The "m" stands for "mandatory." It should be easy to remember that way.

No Initialization (template<class T> class no_init)

This class is very much the same as using the plain type T. It does not force you to initialize it like the need_init and it has no default initializer like auto_init. Actually, when the CONTROLLED_VARS_DEBUG variable is not defined, instead of a template definition we use a straight typedef3

This class has an internal flag called f_initialized. This flag is set to false when creating a variable of this type without an initializer. This flag is checked each time you read the variable. If the flag is false, then the class throws a controlled_vars_error_not_initialized exception.

In other words, the following code works just fine:

rint32_t v(15);
std::cout << v;

Whereas, this one throws an error (at least when the CONTROLLED_VARS_DEBUG flag is defined):

rint32_t v;
std::cout << v;

As you can see, in the second example, v is never set to a value.

It is a good idea to use this type when initializing a variable would by default be a waste of time (i.e. when the variable is very rarely used.) However, it is harder to make sure that your code will always work with such variables4.

Just like the auto_init and need_init, we have a set of predefined variable types as follow:

typedef no_init<bool>              rbool_t;
typedef no_init<char>              rchar_t;
typedef no_init<signed char>       rschar_t;
typedef no_init<unsigned char>     ruchar_t;
typedef no_init<int16_t>           rint16_t;
typedef no_init<uint16_t>          ruint16_t;
typedef no_init<int32_t>           rint32_t;
typedef no_init<uint32_t>          ruint32_t;
typedef no_init<long>              rplain_long_t;
typedef no_init<unsigned long>     rplain_ulong_t;
typedef no_init<int64_t>           rint64_t;
typedef no_init<uint64_t>          ruint64_t;
typedef no_init<float>             rfloat_t;
typedef no_init<double>            rdouble_t;
typedef no_init<long double>       rlongdouble_t;
typedef no_init<size_t>            rsize_t;
typedef no_init<time_t>            rtime_t;

typedef ptr_no_init<bool>          rpbool_t;
typedef ptr_no_init<char>          rpchar_t;
typedef ptr_no_init<signed char>   rpschar_t;
typedef ptr_no_init<unsigned char> rpuchar_t;
typedef ptr_no_init<wchar_t>       rpwchar_t;
typedef ptr_no_init<int16_t>       rpint16_t;
typedef ptr_no_init<uint16_t>      rpuint16_t;
typedef ptr_no_init<int32_t>       rpint32_t;
typedef ptr_no_init<uint32_t>      rpuint32_t;
typedef ptr_no_init<long>          rpplain_long_t;
typedef ptr_no_init<unsigned long> rpplain_ulong_t;
typedef ptr_no_init<int64_t>       rpint64_t;
typedef ptr_no_init<uint64_t>      rpuint64_t;
typedef ptr_no_init<float>         rpfloat_t;
typedef ptr_no_init<double>        rpdouble_t;
typedef ptr_no_init<long double>   rplongdouble_t;
typedef ptr_no_init<size_t>        rpsize_t;
typedef ptr_no_init<time_t>        rptime_t;

Note that all of these types are not available on all platforms.

The "r" letter stands for "random" since the value of a variable of these types is not guaranteed to have a valid value (unless you wrote coverage tests that ran against a version of the code with the CONTROLLED_VARS_DEBUG and did not get any errors... did you?)

IMPORTANT NOTE

It is important to note that when the CONTROLLED_VARS_DEBUG flag is defined, the template is available. However, if the CONTROLLED_VARS_DEBUG flag is NOT defined, then the template disappears. This means if you declare your own type, you also want to check whether the CONTROLLED_VARS_DEBUG flag is defined:

#ifdef CONTROLLED_VARS_DEBUG
typedef no_init<uuid_t> ruuid_t;
#else
typedef uuid_t ruuid_t;
#endif

Limited Controlled Variables

Limited Controlled Variables classes can be used with a constraint over the value of the variable. By default, an integer is 32 bits and accepts any number from -2^31 to 2^31 - 1. However, quite often you want to use an integer for a much smaller number. Say for example that you have a value that represents a percent from 0 to 100. Setting a variable of that type to -1 or 1 million will work in standard C++. With a limited type, you can ask the system to ensure that the value is between 0 and 100 at all time.

IMPORTANT NOTE

The Limited Controlled Variables checks happens ONLY when the the CONTROLLED_VARS_LIMITED flag is defined on your compiler command line or somewhere in your header files before you include the controlled variables. It is a good idea to define that flag whenever you create a debug or pre-release version of your software, and not define it when you create your final software release.

The Limited Controlled Variables has the following default definitions:

typedef limited_auto_init<bool, false, true, false> flbool_t;
typedef flbool_t zlbool_t;
typedef limited_auto_init<bool, false, true, true> tlbool_t;

Be careful when using those since the limited Boolean variables will be limited to just and only true and false. Any other value will raise an exception. Yet in C it is customary to view any value other than zero (0) as true, and zero (0) as false.

The definition uses the minimum and maximum values as two parameters in the template definition. It goes like this:

typedef controlled_vars::limited_auto_init<int32_t, 0, 100, 50> percent_t;

This defines a variable type named percent_t based on int32_t that accepts values from 0 to 100 and is initialized to 50 by default. The following lines will all throw an error at run-time:

percent_t p1(1000);
percent_t p2 = -1;
percent_t p3(100); ++p3;
percent_t p4; p4 += 54;

All the types mentioned before (auto_init, need_init, no_init) support a corresponding limited version. The limited_auto_init class has zero as default for the 4th parameter so it is not required (i.e. the value 50 in our example,) and the other two types do not offer a default parameter (no 4th parameter.)

typedef controlled_vars::limited_need_init<int32_t, -50, 50> weight_t;
typedef controlled_vars::limited_no_init<int32_t, -1000000, 1000000> daily_sales_t;

The Limited class defines two static variables with those default values: MIN_BOUND and MAX_BOUND. So in our previous example using the percent_t type you could write:

percent_t::MIN_BOUND
percent_t::MAX_BOUND

to access the type boundaries (in our example: 0 and 100 respectively.) You can check a value to know whether it is defined within the boundaries using the check() function. If the value is valid, the function returns it casted to the primary type of the limited variable. If the value is outside the boundaries, the function raises an out of bounds exception.

percent_t p;
percent_t::primary_t v = p.check(my_value);

Note that the minimum boundary must be smaller or equal to the maximum boundary. Also, the limited_auto_init type checks that the default value is valid (between the minimum and maximum inclusive.) If any of these tests return false, then the compiler generates an error at compile time (assuming you are using g++ or cl which support our static assert code.)

IMPORTANT NOTE

Like with the standard no_init template, the limited_no_init template is not created when the CONTROLLED_VARS_DEBUG flag is not defined. This means you must check that flag and if not defined, change the limited type definition with a simple typedef. For example, an rpercent_t type would be defined this way:

#ifdef CONTROLLED_VARS_DEBUG
typedef controlled_vars::limited_no_init<int32_t, 0, 100> rpercent_t;
#else
typedef int32_t rpercent_t;
#endif

Known Issues

The ptr() function gives you direct read and write access to the contents of the variable member of the class. This means you can assign an illegal value to the variable since you have direct access and the class has no way to verify that value. It is up to you to either not use the ptr() function or be very careful not to save an invalid value in the variable.

Floating Pointer Support (class fauto_init and limited_fauto_init)

Certain things that you can do with integers cannot be done with floating points. Mainly, having a floating point as an initialization parameter of the template declaration (at least with some older compilers that did not work.) Thus we have a couple of classes just to support floating point types.

template<class T> class fauto_init;
template<class T, T min, T max> class limited_fauto_init;

These templates function the same way as their integer counter parts except that you cannot change the initialization value. Therefore, the only default value of the fauto_init and limited_fauto_init is 0.0.

For compilers that support floating point initialization, then the auto_init and limited_auto_init classes can be used to create floating point types with floating point initializers.

Overhead of using the Controlled Variables

The first main argument that I hear about this header is that it adds a lot of code (over 22,000 lines of C++ declarations...)

You think so too? You couldn't be any further from the truth!

When the debug mode and limited checks are turned off, only the following adds code:

  • The auto_init templates add... the automatic initialization! (i.e. a = 0;)

So... The automatic initialization is most certainly something you want anyway. All of the following are equivalent:

// 1. C++ constructor initialization
int value(3);

// 2. C declaration with default value
int value = 3;

// 3. C declaration followed by assignment
int value;
value = 3;

// 4. C++ auto-initialization
typedef controlled_vars::auto_init<int, 3> value_t;
value_t value;

// 5. C++ constructor initialization (same as #1)
value_t value(3);

Although it can create an overhead in some circumstances, it's not very likely. Your compiler should optimize the following code as expected (i.e. ignore one of the initializations):

zbool_t flag;
flag = false;

Although, frankly, you just shouldn't have that 2nd line of code. No... really!

To push further, the no_init class is 100% the same as using the corresponding type as is since, without the debug flag (CONTROLLED_VARS_DEBUG) the class is not even defined. Instead we define the random types as straight typedef's. For example, the rint32_t type is defined like this:

typedef int32_t rint32_t;

Because of this, some people only make use of the no_init types. However, I still strongly advice you to at least use the need_init type whenever initialization is required (or at least expected.) Although it won't become a simple typedef, the template still becomes the equivalent of a typedef, only it forces you to initialize all such variable members (in debug mode or not, and that's compile time errors!)

Why can I not use the operators?

This is a standard C++ problem. When you declare a class in a namespace, you have to be in that namespace to be able to use those operators. Actually, the problem is not limited to C++ and its derivative. Other languages have the same problem.

The remedy is to add a using namespace like this:

using namespace controlled_vars;

Cons

There are no cons that are strong enough for you to tell me that you shouldn't use these classes everywhere, especially to declare your class members!

However, there are still a few quirks...

Sub-typedefs

At times, you will get errors saying that the compiler cannot decide between function A or function B. For example, if you make use of a variable that's a sub-typedef, the compiler may have a problem choosing between the main type, the first level typedef and the sub-typedef. In many cases, it will be a matter of swapping the parameters for binary operators:

if(var == value) // error because the compiler cannot decide
if(value == var) // works, compiler converts var to value's type

There is now a value() function you can use. So assuming value in the previous example is your controlled variable, it could also be written this way:

if(var == value.value()) // works, both are basic types
Double casting

Once in a while, you have a problem with a cast. A basic integer type can be cast to an enumeration without any specific worries. When defined in a controlled variable, you may need to first change the controlled variable in a basic type then to the enumeration:

e = static_cast<enum something>(static_cast<int32_t>(ctrl_var));

As defined in the previous example, the static cast to int32_t can be replaced with a call to the value() function instead:

e = static_cast<enum something>(ctrl_var.value());
Enumeration Misshandling

Since the constructors and assignment operators do not know about your enumeration type, when you create a controlled variable of an enumeration, no specific constructor or assignment operator is created for your enumeration type. Compilers may generate a warning or even an error letting you know that the enumeration is being cast to an int automatically. You can avoid those warnings or errors by casting your enumerations:

my_control_enum e = static_cast<int32_t>(enum_value);
Floating Point support

Floating points are fully supported.

However, note that the generator does not generate the bitwise and modulo operators for floating point numbers. So these functions generate several lines of errors when used with a float variable, at times making it difficult to find the real cause of the error.

This being said, it does the right thing. Operators that are not compatible with floating points do not get compiled (compile time errors are what you want!)

Streams

At this time we do not have stream support. Things such as the following fail:

zbool_t b;
std::cout << b; // fails, std::cout doesn't know zbool_t

To fix the problem a simple static cast is enough although it can be tedious to cast all the time... You may also want to use the .value() function or temporary variables.

zbool_t b;
std::cout << static_cast<bool>(b);
std::cout << static_cast<zbool_t::primary_type_t>(b);
std::cout << b.value();
bool temp = b;
std::cout << temp;
Memory Expensive

As you can imagine, relatively large headers (some nearly 100Kb) of templates require a lot of memory to compile and optimize. If you use all or at least most of the classes, you may get errors because of memory limits.

I can tell that Microsoft Visual C++ has problems with optimizing code that makes use of these templates. Not too sure why exactly, but the large file generates over 1Gb of data and blows the compiler stack away (so says the compiler: fatal error C1063: compiler limit : compiler stack overflow.) The /Od option makes it compile and link, but you lose all optimizations, obviously. Probably not what you want.

C++ Errors

sizeof() error

error: invalid application of ‘sizeof’ to incomplete type ‘controlled_vars::STATIC_ASSERTION_FAILURE<false>’

This g++ error occurs whenever you try to instantiate a Limited Controlled Variable type which has an invalid constraint. An invalid constraint is a minimum bound that is larger than the maximum bound or the default value that is not within the bounds.

Review your limited_auto_init, limited_need_init, or limited_no_init definitions and make sure the bounds are correctly defined. If you used an unsigned type, a negative value is likely larger than your upper bound.

Updates

  • Version 1.3.4

Renamed some pointer variables "p" instead of "n" or "v".

Include a reset() even if there isn't an initial value, use null() then.

Renamed the default value defined in the trait DEFAULT_VALUE() instead of null() and made
the necessary changes to the main template.

The null() function now returns NULL (0) and not the default value.

Fixed the #ifdef/#endif unicity test in the controlled_vars_version.h.in file.

  • Version 1.3.3

Removed some variable shadowing.

Added bool in constructors and assignment operators. There is a command line flag (--no-bool-constructors) to revert back to the previous behavior.

Added set of templates to handle bare pointers. These can automatically be set to NULL (ptr_auto_init), forced to be initialized (ptr_need_init), or verified before usage (ptr_no_init).

  • Version 1.3.2

    Renamed the last BOOST_... macro so we avoid clashes (warnings) with boost.
     
  • Version 1.3.1

Removed the warning pragma from the version file.

Protect the wchar_t type in case /Zc:wchar_t- is used (Microsoft specific).

Added warning 4005 to the list of warnings to ignore (Microsoft specific).

Changed destination of installation under the controlled_vars folder as intended.

Removed the guards on BOOST_CSTDINT_HPP.

  • Version 1.3.0

This version writes each template in a separate file (instead of one humongous controlled_vars.h file,) making it much easier to use with Microsoft VC++.

Introduced 2 sub-exception classes.

Added a static assert against the boundaries of limited class types to detect invalid boundaries or intialization value at compile time.

Reduced the number of lines of each header file by writing all the functions on a single line (the total number of lines is now around 6,700 whereas before it was over 22,000. Note, however, that this won't make the compiler go faster if you still include the controlled_vars.h file.)

Added a version header so one can check which version is being used.

Added the wchar_t type since all C++ compilers must have that type defined as a basic type.

Added the CONTROLLED_VARS_LIMITED to turn on the limited checks so we can completely optimize those test in a final release build.

Optimized the template definition of the limited types by creating a check() function which avoids the cast to the limited type (i.e. setting a char to 256 is not equivalent to setting it to zero anymore, so you do get errors as would otherwise be expected.)

  • Version 1.2.0

Removed all the functions that floating points do not support. This prevents compiling invalid code (i.e. bitwise operators accepting a floating point as parameters.)

Optimized the limited types, some run-time tests were unnecessary.

  • Version 1.1.0

First publicly published version.

Found an issue? Post your concerns on SourceForge.org. Thank you.

  • 1. I see you coming here... Yes you can initialize variable members, but I declare my variables in my header file (.h) and I then go to my constructor in the code file (.cpp). Guess what... when I first create the class, I think about it. When I update the class later, I forget because I'm working on a function that is not the constructor. Not only that, if my class has three constructors, I'm not unlikely to think of adding the variable to one of them and not the other two! Frankly, this happens to everyone... Using this header is a life saver. Many countless hours of debugging time can be saved just by consistently using Controlled Variables.
  • 2. Starting with 1.3.0 I will use this convention. Previous versions would generate a single header file which ended up being really big and not liked by the Microsoft optimizer.
  • 3. Obviously, if you never define the CONTROLLED_VARS_DEBUG flag while testing your code, you will not catch any of the errors that this class would otherwise help detect. It is important to at least define the flag in your debug version.
  • 4. Of course, an initialized variable is not always that much better since the initialization could be improper at the time the value is used.

Snap! Websites
An Open Source CMS System in C++

Contact Us Directly