Boost logo

Boost :

From: Brian Davis (bitminer_at_[hidden])
Date: 2008-07-21 00:37:02


I looked through boost and if there is a library or facility for data
binding in boost I could not find it... So I created and am submitting a
simple example for review. Please take a look and give some feedback. I
understand that there is quiet a process of review before a library is
included in boost. If there is interest or if there is already on going
work on the concept than I am interested in helping. Here goes:

Data binding should allow for 1 value of primitive type to be changed in
code and have 1 to N values be modified based on the event and optional
conversion functions. i.e. fanout should be allowed
Data binding should allow for cascading changes. Where a->b->c->d where ->
represents a binding link when 'a' changes 'c', 'b', and 'd' should change.
Data binding should be recursion save where a->b->c->a where a change in 'a'
causes a change in 'c', but will not cause a recursive change in 'a'
creating a recursive loop.
Data binding would be useful in the dataflow library and for work being done
on cppgui or for that fact any UI development.

This is my first submittal to this list so please bear with me. I have
taken the liberty to add the boost license to this code... so it is boost's
if the project so chooses to incorporate or enhance the concept. I am not
the originator of data binding concept and make no claim to this. I am only
providing a simple example for discussion and hopefull inclusion of data
binding concept in boost and ....ultimately modify the C++ compiler to
provide for a lighter weight solution (events based on modifications of
integral types) - Ouch ... yes I know what I am asking here... see the last
part of the example output for why I ask this.

// Start snip bind_value.hpp =======================================

//
// (C) Copyright Brian Davis.
//
// Distributed under the Boost Software License, Version 1.0. (See
accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Revision history:
// 19 July 2008
// Created - to support and assist in dataflow and dataflow concepts
//

// This is not currently part of boost! The only reason for the above
copyright is to
// keep these concepts part of the boost development. boost::data_binding
is not
// officially part of boost. The goal of this demo code is to elaborate on
data binding
// and give a simple implementation and an example on how the concept can be
applied within C++.

#ifndef BOOST_DATA_BINDING_HPP

#include <boost/cast.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <vector>
namespace boost { namespace data_binding {

template < class T1, class T2> class bind_values;

template <class T>
class value
{
    friend class bind_values< class T1, class T2>;
    public:
        typedef boost::function< T& (T&) > set_value_function;
        std::vector<set_value_function> set_value_functions;
        T& mv_value;
        // To prevent infinite recursion on a group of three linked
variables a->b->c->a.
        bool mv_entered;
    protected:
    private:

    public:
        value( T& invalue ) : mv_value( invalue ){ mv_entered = false; };

        // Sets require action event handling to update value of binded
type.. gets are transparent
        value& operator=( T& rhs )
        {
            if( set_value_functions.size() && !mv_entered )
            {
                mv_entered = true;
                BOOST_FOREACH( set_value_function set_value,
set_value_functions )
                {
                    mv_value = set_value( rhs );
                }
            }
            else
            {
                // Otherwise simple type cast convert
                mv_value = boost::numeric_cast<T>( rhs );
            }
            mv_entered = false;

            return *this;
        }

        value& operator=( T rhs )
        {

            if( set_value_functions.size() && !mv_entered )
            {
                mv_entered = true;
                BOOST_FOREACH( set_value_function set_value,
set_value_functions )
                {
                    mv_value = set_value( rhs );
                }
            }
            else
            {
                // Otherwise simple type cast convert
                mv_value = boost::numeric_cast<T>( rhs );
            }
            mv_entered = false;
            return *this;
        }

        void add_set_function( set_value_function set_function )
        {
            set_value_functions.push_back( set_function );
        }

        T& operator&( value<T>& value )
        {
            return value.mv_value;
        }

    protected:
    private:

};

template < class T1, class T2>
class bind_values
{
    friend class value<T1>;
    friend class value<T2>;
    public:
        typedef T1 type1;
        typedef T2 type2;
        typedef boost::function< T1& (T1, T2)> convert_to_function;
        typedef boost::function< T2& (T2, T1)> convert_from_function;
        convert_to_function convert_to;
        convert_from_function convert_from;
    protected:
        value<T1>& mv_value1;
        value<T2>& mv_value2;

    private:

    public:
        bind_values
        (
            value<T1>& value1, value<T2>& value2,
            convert_to_function convertTo, convert_from_function convertFrom
        ) :
            convert_to(convertTo), convert_from(convertFrom),
            mv_value1( value1 ), mv_value2( value2 )
        {
            // Hook up set for value 1
            mv_value1.add_set_function( boost::bind(
&bind_values::set_value1, this, _1 ) );

            // Hook up set for value 2
            mv_value2.add_set_function( boost::bind(
&bind_values::set_value2, this, _1 ) );
        }

        bind_values( value<T1>& value1, value<T2>& value2 ) :
            mv_value1( value1 ), mv_value2( value2 )
        {
            // Hook up set for value 1
            mv_value1.set_value = boost::bind( &bind_values::set_value1, _1
);

            // Hook up set for value 2
            mv_value2.set_value = boost::bind( &bind_values::set_value2, _1
);
        }

    protected:
        T1& set_value1( T1& value )
        {
            if( convert_from )
            {
                 // Convert and set value 2
                 mv_value2 = boost::numeric_cast<T2>( convert_from(
mv_value2.mv_value, value ) );
            }
            else
            {
                // Otherwise simple type cast convert
                mv_value2 = boost::numeric_cast<T2>( value );
            }
            // return the value unmodified to the original
container
            return value;

        }

        T2& set_value2( T2& value )
        {
            if( convert_to )
            {
                 // Convert and set value 1
                 mv_value1 = boost::numeric_cast<T1>( convert_to(
mv_value1.mv_value, value ) );
            }
            else
            {
                // Otherwise simple type cast convert
                mv_value1 = boost::numeric_cast<T1>( value );
            }
            // return the value unmodified to the original
container
            return value;

        }

    private:

};

} } // namespace boost::data_binding

template <class T>
std::ostream& operator<<( std::ostream& os, boost::data_binding::value<T>&
rhs )
{
    os << rhs.mv_value;;
    return os;
}

#endif

// end snip bind_value.hpp =======================================

// Start snip bind_value_test.hpp =======================================

//
// (C) Copyright Brian Davis.
//
// Distributed under the Boost Software License, Version 1.0. (See
accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Revision history:
// 19 July 2008
// Created - to support and assist in dataflow and dataflow concepts
//

// This is not currently part of boost! The only reason for the above
copyright is to
// keep these concepts part of the boost development. boost::data_binding
is not
// officially part of boost. The goal of this demo code is to elaborate on
data binding
// and give a simple implementation and an example on how the concept can be
applied within C++.

#include <iostream>
#include <boost/cstdint.hpp>
#include <boost/bind.hpp>
#include <boost/data_binding/bind_value.hpp>

#define MAX_CELSIUS 100
#define MIN_CELSIUS -17.8
#define MAX_RAW_SENSOR_COUNTS 4095

// use of a class is not necessary it is only shown to prove it is
possible. It may
// make more sense just to have conversion functions
class AClass_That_Is_A_Converter
{
    public:
    protected:
        boost::data_binding::value<boost::uint32_t>
rawCelsiusCountsFromSensor;
        boost::data_binding::value<float> calibratedCelsiusDataFromSensor;
        boost::data_binding::value<float> calibratedFahrenheit;

        boost::uint32_t rawSensorValue;
        float fahrenheit;
        float celsius;

        boost::data_binding::bind_values<float, boost::uint32_t>
mv_celsiusToSensorBinder;
        boost::data_binding::bind_values<float, float>
mv_celsiusToFahrenheitBinder;

    private:

    public:
        AClass_That_Is_A_Converter( void ) :
            rawCelsiusCountsFromSensor(rawSensorValue),
            calibratedCelsiusDataFromSensor(celsius),
            calibratedFahrenheit(fahrenheit),
            mv_celsiusToSensorBinder
            (
                calibratedCelsiusDataFromSensor, rawCelsiusCountsFromSensor,
                boost::bind(
&AClass_That_Is_A_Converter::convertCelsiusRawToCalibrated, this, _1, _2 ),
                boost::bind(
&AClass_That_Is_A_Converter::convertCelsiusCalibratedToRaw, this, _1, _2 )
            ),
            mv_celsiusToFahrenheitBinder
            (
                calibratedCelsiusDataFromSensor, calibratedFahrenheit,
                boost::bind(
&AClass_That_Is_A_Converter::convertFahrenheitToCelsius, this, _1, _2 ),
                boost::bind(
&AClass_That_Is_A_Converter::convertCelsiusToFahrenheit, this, _1, _2 )
            )
        {}

        virtual ~AClass_That_Is_A_Converter( void ){}

        void test( void )
        {
            std::cout << "Starting test of data binding" << std::endl;
            try
            {
                std::cout<< "Note raw temp represents the raw value of an
Analog to Digital Converter" << std::endl;
                std::cout<< "The range is from 0-4095 which represents -17.8
- 100 degrees on the Celsius" << std::endl;
                std::cout<< "scale and 0 - 212 degrees on the Fahrenheit" <<
std::endl;

                std::cout<< "Setting calibratedFahrenheit to 70.0 degrees
fahrenheit" << std::endl;
                std::cout<< "calibratedFahrenheit = 70.0f;" << std::endl;
                calibratedFahrenheit = 70.0f;
                std::cout << "What does the celsius value contain? ...
should be ~21.1" << std::endl;
                showValues();

                std::cout << "Now change the raw sensor value and see what
happens to Celsius and Fahrenheit" << std::endl;
                std::cout<< "rawCelsiusCountsFromSensor = 4095;" <<
std::endl;
                rawCelsiusCountsFromSensor = 4095;
                std::cout << "Celsius should equal ~100" << std::endl;
                std::cout << "Fahrenheit should equal ~212" << std::endl;
                showValues();

                std::cout << "Now change celsius see what happens to raw and
farenheit" << std::endl;
                std::cout<< "calibratedCelsiusDataFromSensor = 32.2;" <<
std::endl;
                calibratedCelsiusDataFromSensor = 32.2;
                std::cout << "Fahrenheit should equal ~90 degress" <<
std::endl;
                showValues();

                std::cout << "Note changing original values will not have
any affect in C++ " << std::endl;
                std::cout << "this makes me sad :-( as a class wrapper and
operator overloading" << std::endl;
                std::cout << "is necessary... without changing the
compiler... hint.... hint..." << std::endl;
                std::cout << "fahrenheit = 10;" << std::endl;
                fahrenheit = 10;
                showValues();
                std::cout << "No change in values :-( !!!" << std::endl;

                // Note one to many fanout is possible and the example shows
1 to 2 fanout with Celsius
                // fanning out to Raw and Fahrenheit values. A more
advanced implementation is also possible
                // which includes the use of the lamda library and place
holders. This is just a simple
                // example which I hope will "prime the pump" for work on a
data_binding library with in
                // the Boost development. Most direct use for this library
is within data_flow library.
                // Another use is within the cppgui library which would
allow modification of data to directly
                // affect values of UI controls and display values.

            }
            catch( std::exception& ex )
            {
                std::cout << "Failed with exception: " << ex.what();
            }
            std::cout << "End of data binding test" << std::endl;

        }

        void showValues( void )
        {
            std::cout << std::endl;
            std::cout << "Fahrenheit : " <<
calibratedFahrenheit << std::endl;
            std::cout << "Celsius : " <<
calibratedCelsiusDataFromSensor << std::endl;
            std::cout << "Raw Temp To/From Sensor A/D converter : " <<
rawCelsiusCountsFromSensor << std::endl;
            std::cout << std::endl;
            std::cout << "fahrenheit : " <<
fahrenheit << std::endl;
            std::cout << "celsius : " <<
celsius << std::endl;
            std::cout << "rawSensorValue : " <<
rawSensorValue << std::endl;
            std::cout << std::endl;
            std::cout <<
"====================================================================" <<
std::endl;
        }

    protected:
        float& convertFahrenheitToCelsius
        (
            float& celsius , float& fahrenheit
        )
        {
            return celsius = ((fahrenheit - 32.0) * 5.0) / 9.0;
        }

        float& convertCelsiusToFahrenheit
        (
            float& fahrenheit, float& celsius
        )
        {
            return fahrenheit = celsius * (9.0 / 5.0) + 32.0;
        }

        float& convertCelsiusRawToCalibrated
        (
            float& calibratedValue, boost::uint32_t& rawValue
        )
        {
            return calibratedValue = ( MAX_CELSIUS - MIN_CELSIUS ) *
(rawValue / MAX_RAW_SENSOR_COUNTS) + MIN_CELSIUS;
        }

        boost::uint32_t& convertCelsiusCalibratedToRaw
        (
            boost::uint32_t& rawValue, float& calibratedValue
        )
        {
            // Raw is from 0 to 4095 ( a 12 bit number of an
            // Analog to Digital converter
            // map zero (fahrenheit) to water boiling to raw value of 4095
            return rawValue = MAX_RAW_SENSOR_COUNTS * ( calibratedValue -
MIN_CELSIUS) / ( MAX_CELSIUS - MIN_CELSIUS );
        }

    private:

};

int main(int argc, char **argv)
{

    AClass_That_Is_A_Converter anExampleOfDataBinding;

    anExampleOfDataBinding.test();

}

// end snip bind_value_test.hpp =======================================

// Start snip program output =======================================

Starting test of data binding
Note raw temp represents the raw value of an Analog to Digital Converter
The range is from 0-4095 which represents -17.8 - 100 degrees on the Celsius
scale and 0 - 212 degrees on the Fahrenheit
Setting calibratedFahrenheit to 70.0 degrees fahrenheit
calibratedFahrenheit = 70.0f;
What does the celsius value contain? ... should be ~21.1

Fahrenheit : 70
Celsius : 21.1111
Raw Temp To/From Sensor A/D converter : 1352

fahrenheit : 70
celsius : 21.1111
rawSensorValue : 1352

====================================================================
Now change the raw sensor value and see what happens to Celsius and
Fahrenheit
rawCelsiusCountsFromSensor = 4095;
Celsius should equal ~100
Fahrenheit should equal ~212

Fahrenheit : 212
Celsius : 100
Raw Temp To/From Sensor A/D converter : 4095

fahrenheit : 212
celsius : 100
rawSensorValue : 4095

====================================================================
Now change celsius see what happens to raw and farenheit
calibratedCelsiusDataFromSensor = 32.2;
Fahrenheit should equal ~90 degress

Fahrenheit : 89.96
Celsius : 32.2
Raw Temp To/From Sensor A/D converter : 1738

fahrenheit : 89.96
celsius : 32.2
rawSensorValue : 1738

====================================================================
Note changing original values will not have any affect in C++
this makes me sad :-( as a class wrapper and operator overloading
is necessary... without changing the compiler... hint.... hint...
fahrenheit = 10;

Fahrenheit : 10
Celsius : 32.2
Raw Temp To/From Sensor A/D converter : 1738

fahrenheit : 10
celsius : 32.2
rawSensorValue : 1738

====================================================================
No change in values :-( !!!
End of data binding test

// End snip program output =======================================

This was done to assist Sjepan Rajko in his work on the dataflow library (he
beat me to it :-) ). Dataflow provides the event model data_binding
provides the means to do data conversion between events.

Brian.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk