Introduction
"Trusted Variables" are intended to simplify three very common coding approaches:
- Set variable default value.
- Define range of valid values that may be assigned to the variable.
- Validate assignment (assigned value is in the range) for any assignment.
Usually, these three approaches are implemented in different places: default values are set on declaration or initialization, range definition is semantic only, and assignment validation is implemented for any particular assignment.
"Trusted variables" are trying to bring all three approaches together to the single place - declaration. Once "Trusted variable" is declared, default value is set, and any trial to assign "not in range" value will fail.
Example
Let's assume we have some application, that uses calendar day, as a variable..
Ideally, we would like to define some "trusted" day
, that may be used without any further verification.
TRUSTED_VAR( uint8_t, day, MIN(1), MAX(31), DEFAULT(15) );
Then, when we use, we may refer to variable day
, as a native uint8_t
.
day = 10;
uint8_t wrong_day = 32;
day = wrong_day;
uint8_t temp_day = day;
The Idea
The idea of trusted variable is pretty simple:
For assignment verification, Copy Constructor and Assignment Operator, should involve verification, and for using "like" a native type, Cast Operator to the native type should be implemented.
Implementation
Please NOTE, the following code contains "extra" information like a variable name, and other "string
" related conversions, that may be omitted.
#ifndef __TRUSTED_VAR__
#define __TRUSTED_VAR__
#include <string>
template < typename T > static constexpr T MIN(T min) { return min; }
template < typename T > static constexpr T MAX(T max) { return max; }
template < typename T > static constexpr T DEFAULT(T default) { return default; }
#define TRUSTED_VAR( _type, _name, _min, _max, _default ) \
TrustedVar< _type,_min, _max, _default > _name{#_name};
template
< typename VAR_TYPE, VAR_TYPE MIN_VAL, VAR_TYPE MAX_VAL, VAR_TYPE DEFAULT_VAL >
class TrustedVar
{
public:
static_assert((MAX_VAL >= MIN_VAL), "Invalid Range");
explicit TrustedVar(const char* _val_name):val_name{_val_name}, value{DEFAULT_VAL}
{}
TrustedVar(const TrustedVar& _var)
{
value = _var.value;
}
TrustedVar& operator = (const VAR_TYPE& _value)
{
if ( IsInRange(_value) ) value = _value;
else NotInRange( val_name,
std::to_string(MIN_VAL),
std::to_string(MAX_VAL),
std::to_string(_value)
);
return *this;
}
operator VAR_TYPE()
{
return value;
}
private:
bool IsInRange(const VAR_TYPE& _value)
{
return ((MIN_VAL <= _value) && (MAX_VAL >= _value));
}
private:
const std::string val_name;
VAR_TYPE value;
};
#endif //__TRUSTED_VAR__
I did use MACRO definition for TRUSTED_VAR
for this particular example I wanted to pass variable name for logging purposes (_name{#_name})
If that is not required - that single MACRO may be omitted.
Error Handling
As we saw, on invalid (out of range) assignment will generate an error. The question is how this error will be handled? That really depends on the application, that uses Trusted Variable. As an option, "Out of Range" assignment may generate an exception, may trigger "assert", may generate error log, may set last error, etc.
So ideally, we want to make Error Handling customizable.
Another interesting thing about Trusted Variables, is that usually they are not coming in "singles". In our example with a variable day
, most likely we will have Trusted Variable month
, Trusted Variable year
, and may be hour
, minute
and second
. All of them, I assume, will have the same Error Handling Method.
Providing Error Handling method, as a virtual method on definition of every Trusted Variable, will add another parameter, and more important, will add Memory Complexity (Virtual Table will be created for any single variable) and Performance Complexity (Error handling method will be resolved in Run time).
Instead of this, I suggest to use kind of Compile Time "Namespace Polymorphism".
Let's define Error Handling method, scoped by specific namespace.
namespace DEFAULT_ERROR_HANDLING_NAMESPACE
{
void NotInRange(const std::string& _var_name,
const std::string& range_min,
const std::string& range_max,
const std::string& val)
{
std::cout << "ERROR: Variable \""
+ std::string(_var_name)
+ "\":"
+ " value "
+ val
+ " is out of "
+ range_min
+ '-'
+ range_max
+ " range " << std::endl;
}
}
Now, before using of Trusted Variable, we just need to declare using of the specific namespace, that contains "desired" Error handling implementation. Now all Trusted Variables, while we are "using" this specific namespace, will be handled by the same method (remember, Trusted Variables are not coming "alone" to the "party").
using namespace DEFAULT_ERROR_HANDLING_NAMESPACE;
int main()
{
TRUSTED_VAR( uint8_t, day, MIN(1), MAX(31), DEFAULT(15) );
day = 10;
uint8_t wrong_day = 32;
day = wrong_day;
uint8_t temp_day = day;
return 0;
}
As a result, on Out Of Range assignment,
ERROR: Variable "day": value 32 is out of 1-31 range
will be generated.
Customization
Please see Trusted Variable implementation as a concept. The code may be easily customized, by omitting unnecessary information, like a variable name, removing string
related operations, and/or change of NotInRange
Error Handling signature.
I preferred to avoid run-time polymorphism.
Revisions
- April 27, 2014 -
constexpr
functions instead of decoration macros were added (thanks to Stefan_Lang for the idea)
Compatibility
- Tested on VS2013 + Visual C++ Compiler November 2013 CTP (for constexpr support)
Conclusion
Use them ! :)