3

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?

Virus721
  • 8,061
  • 12
  • 67
  • 123
  • What is allowed on the LHS of your assignment? Only variables or certain kinds of expressions? – dyp May 08 '14 at 11:38
  • This reminds me a bit of the circle-ellipse problem: assignment is an operation that requires mutability of the left operand. That is, an assignment is not "just" an operation, it has stricter requirements. – dyp May 08 '14 at 11:40
  • @dyp On the left hand side of an operation, any sub class of IExpression is allowed. If you use a constant, it should asign a copy of that constant and not do anything, same for the result of an operation, and if you use a variable, it should assign a reference to that variable. At least this is what i'd like to do. Do you think i should treat assignments separatly ? – Virus721 May 08 '14 at 11:43
  • @dyp Yes sorry, i didn't copy the code, i just wrote it, it was a mistake. Corrected it. :) – Virus721 May 08 '14 at 11:43
  • 1
    OT: Avoid [system hungarian notation](http://stackoverflow.com/questions/2292468/class-names-that-start-with-c) – nwp May 08 '14 at 11:44
  • What about distinguishing between `RValue` and `LValue`? They both could be subclasses of `CValue` with operation `assign`, which would work only on LValues. – Piotr Miś May 08 '14 at 11:46
  • @Virus721 did you change the `COperation` to `CScope`? – Re Captcha May 08 '14 at 11:47
  • 1
    @ReCaptcha No i changed CScope to COperation. It was a mistake when i wrote it. – Virus721 May 08 '14 at 11:47
  • @dyp Thanks for your help. I'll try to do something like this. – Virus721 May 08 '14 at 11:55
  • `virtual CValue IExpression::eval( const CScope & ) const = 0;` says that you can't have expressions with side effects (since it's `const`). Assignment is an expression with a side effect. The fundamental problem is that a constant cannot provide a mutating interface, hence it cannot provide an implementation for some `virtual CValue& eval(const CScope&)`. On the other hand, a mutating expression can provide a value. This suggests that (if you want to stick to class hierarchies), there should be a `CConstantExpression` and a derived `CMutatingExpression`. – dyp May 08 '14 at 11:58
  • @dyp Do you mean that i should have two distinct classes : `COperation` and `CMutatingOperation`, inheriting like : `IExpression` -> `COperation` -> `CMutatingOperation` ? Isn't my problem located in the class `CVariable` ? Shouldn't this class provide a method `eval()` returning a reference ? In the case of a mutating operation i would have to check if the LHS item is a variable, and if so, down-cast it to call the non-const eval() ? – Virus721 May 08 '14 at 12:00
  • Hmm maybe I misunderstood what you said in a previous comment: *"If you use a constant, it should asign a copy of that constant and not do anything"* What does that mean? If I have an expression `myConstant = 5`, what is supposed to happen? What is supposed to happen for `42 = 2`? – dyp May 08 '14 at 12:02
  • @dyp 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; – Virus721 May 08 '14 at 12:04
  • So you want this to be a legal expression (nvm your current code) that does not have side effects but evaluates to the rhs? – dyp May 08 '14 at 12:05
  • No i don't really need to do this, it would be useless to be able to do that, but that would be possible with the way i have coded this right now. i just wanted to treat mutating operators like any other, so i could for exemple assign variable inside of if() statements. Like `if( (result = someFunc()) == OK ) { }` – Virus721 May 08 '14 at 12:06
  • What does `(b = a) = 42` do in your language? I.e., is the result of an assignment expression mutable? Similarly, what does `++a = 42` do (if you support the `++` operator)? – dyp May 08 '14 at 12:09
  • @dyp `(b = a) = 42` should first assign `a` to `b`, then assign `42` to `b`. `++a = 42` would increment `a`, then assign `42` to `a`. No ++ operator yet though (or any other non-binary), i'm trying to start by something 'simple'. – Virus721 May 08 '14 at 12:11
  • @nwp - It is better to have *any* notation than none. – SChepurin May 08 '14 at 12:17
  • @nwp - Mixing code and human language is hardly useful and rather childish way to prove your your point. Common notation (any other, if you hate Hungarian) helps to organize the code. – SChepurin May 08 '14 at 13:12
  • @SChepurin I agree that organizing code is useful and my comment is childish. I disagree though that adding a C in front of all class names has any value. There are probably people who honestly think that this helps readability who should probably use it. However, I saw a lot of beginners putting a C in front of class names because they learned it from an outdated book and should therefore think about if that is really what they want. – nwp May 08 '14 at 13:19
  • I think this could be efficiently achieved using expression templates (Supposing you are parsing expressions written in the code of your program, not a runtime parser) – Manu343726 May 08 '14 at 19:48

2 Answers2

1

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;
    }
}
dyp
  • 38,334
  • 13
  • 112
  • 177
  • The temporary objects can be popped at the end of a full-expression as in C++. – dyp May 08 '14 at 12:32
  • Thanks a lot for your help ! I've thought of something that looks a bit like what you did : `virtual const core::CValue & eval( const core::CScope & __roScope ) const = 0;` and `virtual core::CValue & eval( core::CScope & __roScope ) const = 0;` I'm going to study your answer. By the way, why `CScope const& scope` instead of `const CScope &` ? Isn't a reference naturally constant ? – Virus721 May 08 '14 at 12:44
  • Mmmm what i found doesn't seem very OK... I will stick to your answer ! :) – Virus721 May 08 '14 at 12:50
  • `CScope const& scope` and `const CScope & scope` are equivalent. I prefer the former because it's more consistent with other uses of `const`. When consistently using the first version, you can reason about `const` as *the thing preceding the `const` is constant*. For example, `int const*` vs. `int* const`. – dyp May 08 '14 at 12:53
  • Okay i didn't know we would do that. – Virus721 May 08 '14 at 12:54
0

Add a method GetRef to CValue that you use for assignments, and have it throw if CValue isn't a l-value, witch it have to keep track of.
Or make a reference class that is a subtype of CValue, and dynamic_cast it in assignments.

sp2danny
  • 7,488
  • 3
  • 31
  • 53
  • Thanks for your answer. In the way i currently design things a CValue isn't aware or what side of the operator it is. A CValue is just a piece of data (union + data type). IExpression sub classes however reresent the skeleton of the expression and are immune to change since the source code doesn't change at run time. I'm not sure to understand what you have in mind. – Virus721 May 08 '14 at 12:18
  • What I meant was, have `CVariable::eval` return a special kind of `CValue` that knows it is a l-value, via subtyping or members. – sp2danny May 08 '14 at 12:25