Skip to content

martinmoene/WholeValue

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Whole Value Idiom

The whole value idiom[1] supports type-rich programming which, as Bjarne Stroustrup advocates, is an important means to improve software reliability, adding to this: “but most current infrastructure software doesn’t systematically use the techniques I suggest.” [2]

Applying the whole value idiom where you would otherwise be tempted to use built-in types to represent domain values, gains you type checking and expressiveness.

This single-header library provides an easy, zero-overhead way to use the whole value idiom with built-in types in C++.[3]

Example

#include "whole_value.h"

WV_DEFINE_QUANTITY_TYPE( Integer, int )

WV_DEFINE_TYPE( Year, Integer )
WV_DEFINE_TYPE( Day, Integer )

enum Month
{
    January=1, February, March, April, May, June,
    July, August, September, October, November, December
};

class Date
{
public:
   Date( Year year, Month month, Day day ) {}
   // ...
};

int main()
{
    Date today = Date( Year(2012), July, Day(21) );  // OK
//  today = Date( 2012, July, 21 );                  // compile-time error
//  today = Date( Day(21), July, Year(2012) );       // compile-time error
//  today = Date( July, Day(21), Year(2012) );       // compile-time error
}

// g++ -Wall -I../../include/ -I%BOOST_INCLUDE% -o wiki-example1 wiki-example1.cpp
// cl -nologo -W3 -EHsc -I../../include/ -I%BOOST_INCLUDE% wiki-example1.cpp

Other libraries

Similar facilities.

If dimensions play a prime role in your computations, e.g. they are primarily physics-oriented, then you may be better off with libraries such as

  • Boost.Units for zero-overhead dimensional analysis and unit/quantity manipulation and conversion.
  • Michael Kenniston's Quantity Library (Rationale, Quantity folder).
  • PhysUnits-CT, a C++ library for compile-time dimensional analysis and unit/quantity manipulation and conversion. Derived from Michael Kenniston's Quantity Library.
  • PhysUnits-RT, a C++ library for run-time dimensional analysis and unit/quantity manipulation and conversion. Derived from PhysUnits-CT.

Usage

This library provides the following class templates to hold a value:

  • whole_value - just holds a value
  • bits - provides bitwise operations
  • arithmetic - provides arithmetic operations
  • quantity - provides a subset of arithmetic operations
  • safe_bool - a class template that implements the Safe Bool idiom

Depending on your needs, you can define value types that do not interact with each other, or types that allow some interaction, for example with types that are convertible to the underlying type. The following examples range from strict to less so.

Type definitions

#include "whole_value.h"
WV_DEFINE_VALUE_TYPE(Value, double)

This type allows construction (default, initializer, copy) and assignment.

#include "whole_value.h"
WV_DEFINE_BITS_TYPE(Bits, unsigned int)

This type allows construction (default, initializer, copy), assignment, equality comparisons and bitwise operations xor, and, or, shift-left and shift-right.

#include "whole_value.h"
WV_DEFINE_ARITHMETIC_TYPE(Real, double)

This type allows construction (default, initializer, copy), assignment, and all comparison and arithmetic operations.

#include "whole_value.h"
WV_DEFINE_QUANTITY_TYPE(Quantity, double)

This type allows construction (default, initializer, copy), assignment, all comparison operations and a subset of the arithmetic operations. Think of this type as representing a dimension: the available operations do not change the type's 'dimension'. For example, the multiplication Quantity *= Quantity is not available (nor is Quantity * Quantity).

#include "whole_value.h"
WV_DEFINE_SAFE_BOOL_TYPE(SafeBool)

This creates a boolean type that has no adverse interactions with other types (Safe Bool idiom).

#include "whole_value.h"
WV_DEFINE_SAFE_BOOL_TYPE(SafeBool)
WV_DEFINE_SAFE_BOOL_TYPE(SafeBool2)

int main()
{
    SafeBool safebool, safebool_( true );
    SafeBool2 safebool2;

    bool b = safebool;  // Ok
//  int i = safebool;   // compile-time error

    if ( safebool ) {}  // Ok

    if ( safebool == safebool_ ) {}  // Ok
//  if ( safebool == safebool2 ) {}  // compile-time error
}

// g++ -Wall -I../../include/ -I%BOOST_INCLUDE% -o wiki-example2 wiki-example2.cpp && wiki-example2
// cl -nologo -W3 -EHsc -I../../include/ -I%BOOST_INCLUDE% wiki-example2.cpp && wiki-example2

Sub-type definitions

#include "whole_value.h"
WV_DEFINE_TYPE(A, Quantity)
WV_DEFINE_TYPE(B, Quantity)

Use this if you want to distinguish values of type A and B as function arguments, but otherwise want to freely mix values of type A and B in expressions. Here WV_DEFINE_TYPE creates types A and B with Quantity as their common base type.

Interaction with underlying type

To allow interaction with the underlying type or types that are convertible to that, you can define the following preprocessor symbols before inclusion of the header file whole_value.h

// define:
#define WV_ALLOW_CONVERSION_FROM_UNDERLYING_TYPE
// or, one or more of:
#define WV_ALLOW_CONVERSION_FROM_UNDERLYING_TYPE_FOR_VALUE
#define WV_ALLOW_CONVERSION_FROM_UNDERLYING_TYPE_FOR_BITS
#define WV_ALLOW_CONVERSION_FROM_UNDERLYING_TYPE_FOR_ARITHMETIC
#define WV_ALLOW_CONVERSION_FROM_UNDERLYING_TYPE_FOR_QUANTITY

#include "whole_value.h"

Output

To make a type defined with WV_DEFINE... streamable, define the desired operator with or without io manipulators.

#include "whole_value.h"
#include <iomanip>
#include <iostream>

WV_DEFINE_QUANTITY_TYPE(Quantity, double)
WV_DEFINE_TYPE(Acceleration, Quantity)
WV_DEFINE_TYPE(Speed, Quantity)

WV_ADD_STREAM_INSERTION_OPERATOR(Quantity)
// or
WV_ADD_STREAM_INSERTION_OPERATOR_IOMANIP(Quantity, std::fixed << std::setprecision(2) )

int main()
{
    Acceleration acc(9.8);
    Speed spd(330);
    std::cout << "acc:" << acc << ", spd:" << spd << std::endl;
}

// g++ -Wall -I../../include/ -I%BOOST_INCLUDE% -o wiki-example3 wiki-example3.cpp && wiki-example3
// cl -nologo -W3 -EHsc -I../../include/ -I%BOOST_INCLUDE% wiki-example3.cpp && wiki-example3

To stream sub types, you only need to define a streaming operator for its base class.

Convenience functions

// absolute value:

template <typename T, typename U> inline whole_value<T,U> abs( whole_value<T,U> const & x );
template <typename T, typename U> inline  arithmetic<T,U> abs(  arithmetic<T,U> const & x );
template <typename T, typename U> inline    quantity<T,U> abs(    quantity<T,U> const & x );

// value as underlying type:

template <typename T, typename U> inline T to_value( whole_value<T,U> const & x );
template <typename T, typename U> inline T to_value(        bits<T,U> const & x );
template <typename T, typename U> inline T to_value(  arithmetic<T,U> const & x );
template <typename T, typename U> inline T to_value(    quantity<T,U> const & x );

// value as integer (long):

template <typename T, typename U> inline long to_integer( whole_value<T,U> const & x );
template <typename T, typename U> inline long to_integer(  arithmetic<T,U> const & x );
template <typename T, typename U> inline long to_integer(    quantity<T,U> const & x );

// value as real (double):

template <typename T, typename U> inline double to_real( whole_value<T,U> const & x );
template <typename T, typename U> inline double to_real(  arithmetic<T,U> const & x );
template <typename T, typename U> inline double to_real(    quantity<T,U> const & x );

Dependencies

Boost.Operators can be used to generate part of the logical and arithmetic operators of the bits, arithmetic and quantity class templates in this library. Note however that this is optional. If Boost.Operators is not used, the relevant operators are directly included in the classes in the conventional way.

To use Boost.Operators, #define WV_USE_BOOST_OPERATORS before inclusion of header file whole_value.h.

#define WV_USE_BOOST_OPERATORS // optional
#include "whole_value.h"

Boost version 1.46.1 is known to work with the compilers mentioned below.

Performance

Relative performance (higher is better)

Compiler        Option : double : Real  Real.B  Real.F : Derived  Derived.B  Derived.F
-----------------------+--------+----------------------+------------------------------
GCC 4.5.2         -O2  :  1     :  1     1       1     :   0.7      0.7        0.7
MS VC6/VS6        -O2  :  1.2   :  1.2   0.1     0.1   :   0.95 a   0.05 b     0.05 c
MS VC8/VS2005     -O2  :  1.1   :  0.4   0.4     0.4   :   0.4      0.4        0.4
MS VC2010/VS2010  -O2  :  1.2   :  0.4   0.4     0.4   :   0.4      0.4        0.4

                                                         a:0.009  b:0.01     c:0.007
.B - with -DWV_USE_BOOST_OPERATORS
.F - with -DWV_DEFINE_OPERATORS_IN_TERMS_OF_A_MINIMAL_NUMBER_OF_FUNDAMENTAL_OPERATORS

WV_DEFINE_ARITHMETIC_TYPE(Real, double)
WV_DEFINE_TYPE(Derived, Real)

Measured on AMD Athlon 64 X2 Dual Core Processor 5600+, 64kB L1 Data, 64kB L1 Instruction, 512kB L2, 3.2 GB RAM
a, b, c  on Intel Pentium M Processor, 32kB L1 Data, 32kB L1 Instruction, 2MB L2, 1.50 GHz

Compilers known to work

  • GCC 4.5.2
  • MS VC6/VS6 - requires using namespace wv; for convenience functions and streaming operators (VC6 lacks ADL).
  • MS VC8/VS2005
  • MS VC2010/VS2010

Ideas for improvement

  • Immutable type variants

References

[1] The whole value idiom was identified by Ward Cunningham[4] and Martin Fowler[5].
[2] Bjarne Stroustrup. Software Development for Infrastructure. Computer, January 2012.
[3] This library is inspired on the presentation by Mark Radford: Design Experiences in C++, p.23. 2005.
[4] Ward Cunningham. The CHECKS Pattern Language of Information Integrity. C2 Wiki, 1994.
[5] Martin Fowler. Quantity Pattern. Website.