Al momento della compilazione, No, nella migliore delle ipotesi, ci si ritroverà con qualche brutto modello e hack macro che sarà ancora fortemente limitata. Se il tuo pensiero è in fase di compilazione, non leggere il resto della risposta
Da un livello Utente (in fase di esecuzione)? Si, puoi. Tuttavia, la logica è abbastanza semplice, devi solo trovare un modo per creare e mantenere pragmaticamente gli invarianti di un albero delle espressioni. Richiede un po 'di lavoro per realizzarlo.
Quindi, consente, affrontare la logica ... Per semplicità, definiamo alcuni termini di base qui per adattare il nostro intento
- Un
operator
è una funzione che richiede al massimo 2 operands
in modo tale che quando viene chiamato , produce un risultato che è un altro operand
- Un
operand
è un oggetto di interesse, nel tuo caso, numeri, più precisamente double
.Può essere prodotto da te o un expression
- Un
expression
è un oggetto che richiede al massimo 2 operands
ed un operator
, e produce un conseguente operand
chiamando la funzione operator
.
ho fatto alcuni disegni per illustrare questo ....

Come si può vedere, le frecce indica la direzione della conoscenza.
- Un
Operand
conosce tutte le espressioni la sua coinvolti in.
- Un
Expression
conosce la Operands
ha prodotto.
Quindi, cerchiamo di dare loro alcune identità ...

diciamo, si è creato Operand 1
, Operand 2
, Operand 4
. E hai iniziato la costruzione di questo albero di espressione in questo ordine:
si è creato un rapporto (un Expression
) fra Operand 1
e Operand 2
che è rappresentato dal Expression1
.
Expression1
utilizza il Operator
è stato costruito con a produrre il suo risultato, Operand 3
È combinato risultante Operand 3
con il creato Operand 4
in una nuova espressione Expression2
per la produzione di un altro risultato, Operand 5
Ora, vediamo cosa succede quando decidiamo di modificare Operand 1
.

Come si può vedere, l'operando modificato in modo ricorsivo passare attraverso e aggiornare tutte le sottoespressioni il cui risultato dipende da esso (direttamente o per delega).
Ora, che abbiamo questa idea molto semplice, come possiamo farlo. Ci sono diversi modi per implementarlo, più è generico e flessibile, meno performante sarà (in termini di memoria e velocità)
Ho fatto una semplice implementazione di seguito (ovviamente, lontano da qualsiasi cosa ottimale).
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;
}
l'output è:
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
Guardalo Live on Coliru
Per semplici integral
tipi, il mio utilizzo di shared_ptr
era un peso inutile, ho potuto effettivamente fare questo con i puntatori normali. Ma questa implementazione tende a generalizzare sul tipo di typename T
.
Altre cose da pensare ...
- scelte API
- Utilizzo della memoria
- evitare il rilevamento di ciclo (gli aggiornamenti infinitamente ricorsivi)
- ... ecc
Commenti , Critiche e suggerimenti sono benvenuti. :-)
Buona domanda! ... Questo tipo di problema si trova nella modellazione parametrica ... In genere c'è un sacrificio tra generalità e perfomance. La risposta di Jarod42 è buona. Ma per il cambio automatico, richiede un po 'di lavoro – WhiZTiM
Quando dici "ottiene automaticamente" vuoi che il terzo oggetto venga avvisato quando uno dei primi due oggetti viene modificato o sei soddisfatto con un modello di pull in cui rilevi le modifiche quando ne hai bisogno? –
Grazie a tutti per le vostre risposte. @ChrisDrew Quello che intendo è che se cambio uno dei primi due oggetti, il valore assegnato al terzo oggetto cambia anche senza ulteriori operazioni esplicite. – Eman