I need to create classes to represent expressions such as someVar=2*(5+3)^5. For that i want to have the following classes :
class IExpression
{
public:
virtual ~IExpression( void ) { }
virtual CValue eval( const CScope & ) const = 0;
};
class CConstant : public IExpression
{
protected:
const CValue _value;
public:
virtual CValue eval( const CScope & ) const { return this->_value; }
};
class COperation : public IExpression
{
protected:
const IExpression * const _left;
const operator_e _eOper;
const IExpression * const _right;
public:
virtual ~COperation( void );
virtual CValue eval( const CScope & ) const;
};
class CVariable : public IExpression
{
protected:
const string _name;
public:
virtual CValue eval( const CScope & scope ) const { return scope.access( this->_name ); }
};
And also assume that I have a class CScope, used by CVariable::eval() to access variables :
class CScope
{
protected:
map<string, CValue> _mVars;
public:
inline const CValue & access( const string & name ) const;
inline CValue & access( const string & name );
};
I have a problem here. Imagine that I have an operation using an operator that requires to access the value by reference so that it can be set, like = or +=. For an assignment I would do :
CValue COperation::eval( const CScope & scope ) const
{
switch( this->_eOper )
{
// ...
case E_ASSIGN:
return this->_left->eval( scope ) = this->right->eval( scope );
}
}
There problem here is that this->_left->eval( scope ), where _leftis pointing to a CVariable, is going to return a copy of the CValue identified by the name of that variable, and the assignment isn't going to work.
I could change the signature of the method eval() to make it return a CValue &, but that would cause a problem because in the case of a COperation, the result of the operation is a temporary variable that gets destroyed as soon as COperation::eval() returns.
Do you have an idea about how I could do that?
From a discussion in the comments to the OP:
What is supposed to happen for 42 = 2?
Well
CConstant::eval()would return a temporary copy of the member CValue which would be assigned, but that would not do anything since it would be a temporary value and not the member itself that would get assigned. It would be like doing :3 + 5 = 10;
I do not particularly like this design, because it allows error-prone or nonsensical expressions, but let's try to implement it:
class IExpression
{
public:
virtual ~IExpression( void ) { }
virtual CValue& eval( CScope & ) const = 0;
};
With this interface, we can mutate any return value of an expression. Of course, it requires using temporary objects. You can push them to your stack in CScope, which requires modifying the scope object. For example:
class CConstant : public IExpression
{
protected:
const CValue _value;
public:
virtual CValue& eval( CScope & scope ) const
{
return scope.push(_value);
}
};
The variable stays roughly the same:
class CVariable : public IExpression
{
protected:
const string _name;
public:
virtual CValue& eval( CScope & scope ) const
{ return scope.access( this->_name ); }
};
Then, assignment just works the way you've written it:
CValue COperation::eval( CScope & scope ) const
{
switch( this->_eOper )
{
// ...
case E_ASSIGN:
return this->_left->eval( scope ) = this->right->eval( scope );
}
}
If you don't want to create copies of the values at all times, you can restrict copying a constant when it's used at the LHS of an assignment.
class IExpression
{
public:
virtual ~IExpression( void ) { }
virtual CValue rvalue( CScope const& ) const = 0;
virtual CValue& lvalue( CScope & ) const = 0;
};
class CConstant : public IExpression
{
protected:
const CValue _value;
public:
virtual CValue& lvalue( CScope & scope ) const
{
return scope.push(_value);
}
virtual CValue rvalue( CScope const& scope ) const
{
return _value;
}
};
CValue COperation::eval( CScope & scope ) const
{
switch( this->_eOper )
{
// ...
case E_ASSIGN:
return this->_left->lvalue( scope ) = this->right->rvalue( scope );
}
}
We can also create the temporaries more locally:
class CMutableValue
{
private:
union { CValue _cpy; };
bool _tmp;
public:
CValue& _ref;
CMutableValue(CValue & var)
: _tmp(false), _ref(var) {}
CMutableValue(CValue const & val)
: _cpy(val), _tmp(true), _ref(_cpy) {}
CMutableValue(CMutableValue const& source)
: _tmp(source._tmp), _ref( source._tmp ? _cpy : source._ref )
{
if(source._tmp) new( (void*)&_cpy ) CValue(source._ref);
}
// delete in C++11, or private in C++03
CMutableValue& operator=(CMutableValue const&) = delete;
};
This tries to avoid some of the lifetime issues with temporaries, but I think it's still dangerous.
class IExpression
{
public:
virtual ~IExpression( void ) { }
virtual CMutableValue eval( CScope const& ) const = 0;
};
class CConstant : public IExpression
{
protected:
const CValue _value;
public:
virtual CValue& eval( CScope & scope ) const
{
return CMutableValue(_value);
}
};
CValue COperation::eval( CScope & scope ) const
{
switch( this->_eOper )
{
// ...
case E_ASSIGN:
return this->_left->eval( scope )._ref
= this->right->eval( scope )._ref;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With