16

I asked a similar question a couple of hours ago about connecting two elements of a vector. Now, I would like to make my question more general. Let's assume that we have two objects of the type double namely double d1, d2. We want the third object (double d3) to obtain the value of d1+d2, such that if we change d1 or d2, then d3 automatically obtains the new value of d1+d2. How can we do it in C++?

This is what I mean:

int main(){
double d1,d2,d3;
d1=4;
d2=7;

//some operations to make d3=d1+d2

std::cout<<d3<<endl;// I want it to print 11
d2=-4;
std::cout<<d3<<endl;//Now without any further operations between these line, it should print 0
return 0;     
}

Thanks.

Eman
  • 165
  • 1
  • 1
  • 8
  • 1
    Good Question! ...This kind of problem is found in parametric modeling... There's typically a sacrifice between generality and perfomance. Jarod42's answer is good. But for automatic change, it requires a bit of work – WhiZTiM Apr 30 '16 at 14:07
  • When you say "automatically obtains" do you want the third object to be notified when one of the first two objects are changed or are you happy with a pull model where you detect changes when you need them? – Chris Drew Apr 30 '16 at 14:18
  • Thanks everyone for your answers. @ChrisDrew What I mean is that if I change any of the first two objects, the value assigned to the third object also changes without any further explicit operations. – Eman Apr 30 '16 at 15:10
  • @Eman, You can't do it at compile time without some Life-threatening template and Macro tricks. But if you meant from a user level (or at runtime), You may be interested in my answer – WhiZTiM May 01 '16 at 01:20

5 Answers5

11

You can create a wrapper, as a lambda:

double d1 = 0, d2 = 0;
auto l = [&](){ return d1 + d2; };

l(); // 0
d1 = 40;
d2 = 2;
l(); // 42

If you want that all the variables has the same type, you may add a type-erasure wrapper as std::function:

std::function<double()> f1 = [] { return 0; };
std::function<double()> f2 = [] { return 0; };
std::function<double()> sum = [&] { return f1() + f2(); };

std::cout << sum() << std::endl; // 0
f1 = [] { return 40; };
f2 = [] { return 2; };

std::cout << sum() << std::endl; // 42
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    You should have a look at the OP's [original question](http://stackoverflow.com/questions/36952849/how-to-connect-two-elements-of-a-vector). A lambda won't really work for that case. – πάντα ῥεῖ Apr 30 '16 at 14:18
  • 1
    @Jarod42 Thanks for the answer. The only problem is that I want the third object to be of the same type as the first two ones, but in this code "l" has a different type than double. – Eman Apr 30 '16 at 15:24
  • @Eman Than post more code of how you are expecting to use it. You can't have double which will by itself for "no particular reason" – Zereges Apr 30 '16 at 15:45
  • @Zereges I added a piece of code to elaborate what I exactly mean by automatic change of the third object. – Eman Apr 30 '16 at 16:29
  • @Eman: Some change to make all variable the same type (but not `double`) – Jarod42 Apr 30 '16 at 16:29
  • @Jarod42 Thanks a lot. At least I got an insight from your code as to which direction I might go. – Eman Apr 30 '16 at 16:38
9

Your problem is the classical motivation for parameter binding.

#include <iostream>
#include <functional>

//generic add
template <typename T>
void Add(T x, T y, T & z){
  z = x + y;
}

int main(){

  //z will change automatically in function call
  double z = 0;

  //bind z as the result
  using namespace std::placeholders;
  auto add = std::bind(Add<double>, _1, _2, std::ref(z));

  //z is implicity passed and changed
  add(6,4);

  //outputs 10
  std::cout << z << '\n';
}

bind and reference wrappers can help achieve the functionality you are after.

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
7

Write a wrapper, which will store pointers to doubles (as recommended in your original question). Beware, that this will not work, if doubles will go out of scope but counter would not. Also, you can overload conversion to T operator to get rid of total() function.

template<typename T>
class counter
{
public:
    void regist(T& d)
    {
        refs.push_back(&d);
    }

    T total()
    {
        T total{};
        for (auto d : refs)
            total += *d;
        return total;
    }

private:
    std::vector<T*> refs;
};

int main(int argc, char* argv[])
{
    double d1 = 1.6;
    double d2 = 7.2;
    double d3 = 1e-4;
    counter<double> cnt;
    cnt.regist(d1);
    cnt.regist(d2);
    cnt.regist(d3);
    std::cout << cnt.total() << std::endl; // 8.8001
    d2 = -7.1;
    std::cout << cnt.total() << std::endl; // -5.4999
}
Zereges
  • 5,139
  • 1
  • 25
  • 49
5

At compile time, No, at best, you'll end up with some nasty template and Macro hacks that will still be severely limited. If your thought is on compile-time, don't read the rest of the answer

From a User level (at run time)? Yes, you can. Though, the Logic is quite simple, you are just to find a way to pragmatically create and maintain the invariants of an Expression Tree. It requires a bit of work to get it actualized.

So, Lets, tackle the Logic... For simplicity, let us define some basic terms here to fit our intent

  • An operator is a function that requires at most 2 operands such that when called, it produces a result which is another operand
  • An operand is an object of interest, in your case, numbers, more precisely double. It can be produced by you or an expression
  • An expression is an object that takes at most 2 operands and an operator, and produces a resulting operand by calling the operator function.

I did some drawings to illustrate this....

Expression Tree

As you can see, the arrows shows the direction of knowledge.

  • An Operand knows all the expressions its involved in.
  • An Expression knows the Operands it produced.

So, Let us give them some identities...

Expression Tree

Let us say, you created Operand 1, Operand 2, Operand 4. And you started building up this expression tree in this order:

  1. You created a relationship (an Expression) between Operand 1 and Operand 2 which is represented by Expression1.

  2. Expression1 uses the Operator it was constructed with to produce its result, Operand 3

  3. You combined the resulting Operand 3 with your created Operand 4 into a new expression Expression2 to produce another result, Operand 5


Now, let us see what happens when we decide to modify Operand 1.

Effect of modifying one Operand

As you can see, the modified operand will recursively go through and update all subexpressions whose result depends on it (whether directly or by proxy).


Now, that we've got this very simple idea, how do we go about it. There are a number of ways you can implement it, the more generic and flexible it is, the less performant it will likely be (in terms of Memory and Speed)

I did a simple implementation below (Obviously, far from anything Optimal).

template<typename T>
class Operand;

template<typename T>
class Expression {
    std::shared_ptr<Operand<T>> m_operand1;
    std::shared_ptr<Operand<T>> m_operand2;
    std::shared_ptr<Operand<T>> m_result;
    T (*m_operator)(const T&, const T&);

    friend class Operand<T>;

    public:
    Expression(
               T(*operator_func)(const T&, const T&),
               std::shared_ptr<Operand<T>> operand_1,
               std::shared_ptr<Operand<T>> operand_2) :
                   m_operand1(operand_1),
                   m_operand2(operand_2),
                   m_result(std::make_shared<Operand<T>>(T{})),
                   m_operator(operator_func)
    {
    }

    void update(){
        m_result->value() = m_operator(m_operand1->value(), m_operand2->value());
        m_result->update();
    }

    std::shared_ptr<Operand<T>>& result() { return m_result; }

};

template<typename T>
class Operand {
    T val;
    std::vector<std::shared_ptr<Expression<T>>> expressions;
    friend class Expression<T>;

    public:

    Operand(T value) : val(value) {}

    T& value() { return val; }

    void update(){
        for(auto& x : expressions)
            x->update();
    }

    static std::shared_ptr<Operand<T>> make(const T& t){
        return std::make_shared<Operand<T>>(t);
    }

    static std::shared_ptr<Operand<T>>
        relate(
               T(*operator_func)(const T&, const T&),
               std::shared_ptr<Operand<T>> operand_1,
               std::shared_ptr<Operand<T>> operand_2
               ){
        auto e = std::make_shared<Expression<T>>(operator_func, operand_1, operand_2);
        operand_1->expressions.push_back( e );
        operand_2->expressions.push_back( e );
        e->update();
        return e->result();
    }
};

//template<typename T>
//double add(const double& lhs, const double& rhs){ return lhs + rhs; }

template<typename T>
T add(const T& lhs, const T& rhs){ return lhs + rhs; }

template<typename T>
T mul(const T& lhs, const T& rhs){ return lhs * rhs; }

int main()
{
    using DOperand  = Operand<double>;

    auto d1 = DOperand::make(54.64);
    auto d2 = DOperand::make(55.36);

    auto d3 = DOperand::relate(add<double>, d1, d2);
    auto d4 = DOperand::relate(mul<double>, d3, d2);



    //---------------PRINT------------------------//
    std::cout << "d1 = " << d1->value() << "\nd2 = " << d2->value()
              << "\nd3 = d1 + d2 = " << d3->value() << "\nd4 = d3 * d2 = "
              << d4->value() << std::endl;


    //---------------UPDATE ONE VARIABLE------------------------//
    std::cout << "\n\n====================\n" << std::endl;
    std::cout << "changed d1 from " << d1->value() << " to ";
    d1->value() = -863.2436356;
    d1->update();
    std::cout << d1->value() << "\n\n=======================\n\n";



    //---------------PRINT------------------------//
    std::cout << "d1 = " << d1->value() << "\nd2 = " << d2->value()
              << "\nd3 = d1 + d2 = " << d3->value() << "\nd4 = d3 * d2 = "
              << d4->value() << std::endl;


    // *******************************************
    std::cout << "\n\n\n\n\nSizeof(Operand<int>) = " << sizeof(Operand<int>)
              << "\nSizeof(Expression<int>) = " << sizeof(Expression<int>) << std::endl;
}

The Output is:

d1 = 54.64
d2 = 55.36
d3 = d1 + d2 = 110
d4 = d3 * d2 = 6089.6

====================
changed d1 from 54.64 to -863.244
=======================

d1 = -863.244
d2 = 55.36
d3 = d1 + d2 = -807.884
d4 = d3 * d2 = -44724.4

See it Live on Coliru

For simple integral types, my usage of shared_ptr was an overkill, I could actually do this with normal pointers. But this implementation tends to generalize on the type of typename T.

Other things to think of...

  • API choices
  • Memory usage
  • Avoiding Cycle detection (infinitely recursive updates)
  • ...etc

Comments, Criticisms and suggestions are welcomed. :-)

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • Thanks a lot. This is really helpful. Changing the value at run time is sufficient for what I want to do. – Eman May 01 '16 at 06:37
4

There is no way to do this without changing the type of d1 and d2 into something observable. You can do something like this.

#include <iostream>
#include <boost/signals2.hpp>

class ObservableDouble
{
public:    
    boost::signals2::signal<void()> sigChanged;

    void operator=( double val ) 
    {
        d = val;
        sigChanged();
    }

    operator double() const { return d; } 

private:    
    double d;    
};

int main() 
{
    ObservableDouble d1;
    ObservableDouble d2;        
    double d3;

    auto add = [&](){ d3 = d1 + d2; };

    d1.sigChanged.connect( add );
    d2.sigChanged.connect( add );

    d1 = 4.0;
    d2 = 7.0;        
    std::cout << d3 << std::endl; // prints 11
    d2 = -4.0;
    std::cout << d3 << std::endl; // prints 0
}

Live on Coliru

Thomas
  • 4,980
  • 2
  • 15
  • 30
  • +1 for signals - when the number of objects observed to make a result is much larger than 2 this approach is much more efficient. – Rafał Rawicki May 02 '16 at 09:00