Sign in to follow this  
Misery

Expression templates for arguments of different types

Recommended Posts

Misery    354

I am implementing expression templates for Vector3<> template class in C++. Actually I have it working very well, but only when arguments of the same type are involved. Lets say Vector3<double> and Vector3<double> or double scalar and Vector3<double>, etc. In the case of for example multiplying Vector3<double> and Vector3<float> compiler prompts error, as it these are two different objects and it is unable to perform float to double conversion by itself. 

To solve this issue I had two ideas:

1. Define all necessary classes and operators in terms of possible interactions float and double, float and int etc. Which is very very wrong approach

2. Create templates using auto and decltype

 

I managed to achieve somewhat a success. I can create a Vector3<anything1> and perform operations on it with other Vector3<anything2>. So it is a clean and nice solution, as it does not require to define all possible conversions. However when I try to perform an operation between any scalar and Vector3<anything> I get errors.

 

Different operators (in the code below it would be Vector3<DATA_TYPE_L>*Vector3<DATA_TYPE_R> and scalar DATA_TYPE_L*Vector3<DATA_TYPE_R>) interfere. Separately both operators work fine. However when both are present compiler always chooses scalar*Vector3<> operator, and of course fails to deduce arguments when Vector3<>*Vector3 operator should be used.

I managed to solve this situation only by providing additional class Scalar<DATA_TYPE> which embraces the scalar value. However then I have to call some function creating Scalar class explicitly out of let's say double. This is something I would like to avoid.

 

So I thought of two solutions:

1. Make the operator create Scalar class transparently out of float or double etc. so no explicit usage of function creating Scalar would be needed

2. Redesign the code so operators wouldn't interfere

However I did not manage to achieve any of those. Any help would be very appreciated.

The Code below works fine in VS2015. Remove comments to get mentioned error.

typedef  int SIZE_INT;

template <class DATA_TYPE, class Expression>
class Vector3Expression
{
public:
    auto operator[](SIZE_INT i) const { return static_cast<Expression const &>(*this)[i]; }

    operator Expression&() { return static_cast<      Expression&>(*this); }
    operator Expression const&() const { return static_cast<const Expression&>(*this); }
};


template <class DATA_TYPE>
class Vector3 :public Vector3Expression<DATA_TYPE, Vector3<DATA_TYPE> >
{
public:

    DATA_TYPE Data[3];

    Vector3() {}
    Vector3(const Vector3& that)
    {
        Data[0] = that.Data[0];
        Data[1] = that.Data[1];
        Data[2] = that.Data[2];
    }

    Vector3(Vector3&& that)
    {
        std::move(that.Data, 3, Data);
    }

    template <class OTHER_DATA_TYPE, class Expression>
    Vector3(Vector3Expression<OTHER_DATA_TYPE, Expression> const& VE)
    {
        Expression const& ve = VE;
        Data[0] = ve[0];
        Data[1] = ve[1];
        Data[2] = ve[2];
    }

    //access operators
    DATA_TYPE&  operator[](SIZE_INT i) { return Data[i]; }
    DATA_TYPE  operator[](SIZE_INT i) const { return Data[i]; }
    DATA_TYPE&  operator()(SIZE_INT i) { return Data[i]; }
    DATA_TYPE  operator()(SIZE_INT i) const { return Data[i]; }


    //operators
    Vector3<DATA_TYPE>& operator=(Vector3<DATA_TYPE> &&that)
    {
        if (&that != this)
        {
            std::swap(Data[0], that.Data[0]);
            std::swap(Data[1], that.Data[1]);
            std::swap(Data[2], that.Data[2]);
        }
        return *this;
    }

    Vector3<DATA_TYPE>& operator=(const Vector3<DATA_TYPE> &that)
    {
        if (&that != this)
        {
            Data[0] = that.Data[0];
            Data[1] = that.Data[1];
            Data[2] = that.Data[2];
        }
        return *this;
    }

    SIZE_INT  Length() const {
        return SIZE_INT(3);
    }
    };


    template <class DATA_TYPE_LHS, class DATA_TYPE_RHS, class Lhs, class Operator, class Rhs>
    class Vector3Vector3Operation : public Vector3Expression<decltype(Operator::Apply(DATA_TYPE_LHS(), DATA_TYPE_RHS())), Vector3Vector3Operation<DATA_TYPE_LHS, DATA_TYPE_RHS, Lhs, Operator, Rhs> >
    {
    private:
        Lhs const& vL;
        Rhs const& vR;
    public:

        Vector3Vector3Operation(Vector3Expression<DATA_TYPE_LHS, Lhs> const & lhs, Vector3Expression<DATA_TYPE_RHS, Rhs> const & rhs) : vL(lhs), vR(rhs) {}
        auto operator[](SIZE_INT i) const { return Operator::Apply(vL[i], vR[i]); }
    };

    template <class DATA_TYPE_LHS, class DATA_TYPE_RHS, class Operator, class Vec>
    class ScalarVector3Operation : public Vector3Expression<decltype(Operator::Apply(DATA_TYPE_LHS(), DATA_TYPE_RHS())), ScalarVector3Operation<DATA_TYPE_LHS, DATA_TYPE_RHS, Operator, Vec> >
    {
    private:
        const DATA_TYPE_LHS S;
        Vec const& V;

    public:

        ScalarVector3Operation(const DATA_TYPE_LHS s, Vector3Expression<DATA_TYPE_RHS, Vec> const& v) : V(v), S(s) {}
        auto operator[](SIZE_INT i) const { return Operator::Apply(S, V[i]); }
    };


    namespace meta {

        template<class DATA_TYPEL, class DATA_TYPER = DATA_TYPEL>
        struct Multiply
        {
            inline static auto Apply(DATA_TYPEL a, DATA_TYPER b)->decltype(a*b) { return a*b; }
        };
    }

    template <class DATA_TYPEL, class DATA_TYPER, class LExpression, class RExpression>
    Vector3Vector3Operation<DATA_TYPEL, DATA_TYPER, LExpression, meta::Multiply<DATA_TYPEL, DATA_TYPER>, RExpression> const
        operator*(Vector3Expression<DATA_TYPEL, LExpression> const& u, Vector3Expression<DATA_TYPER, RExpression> const& v)
    {
        return Vector3Vector3Operation<DATA_TYPEL, DATA_TYPER, LExpression, meta::Multiply<DATA_TYPEL, DATA_TYPER>, RExpression>(u, v);
    }
    /*
//operator causing trouble
    template <class DATA_TYPE_LHS, class DATA_TYPE_RHS, class RExpression>
    ScalarVector3Operation<DATA_TYPE_LHS, DATA_TYPE_RHS, meta::Multiply<DATA_TYPE_LHS, DATA_TYPE_RHS>, RExpression> const
    operator*(const DATA_TYPE_LHS s, Vector3Expression<DATA_TYPE_RHS, RExpression> const& v)
    {
    return ScalarVector3Operation<DATA_TYPE_LHS, DATA_TYPE_RHS, meta::Multiply<DATA_TYPE_LHS, DATA_TYPE_RHS>, RExpression>(s.value, v);
    }
    */

    int main()
    {

        Vector3<double> d, x, u;
        Vector3<float> f;

        double s = 0.25;

        d[0] = 1; d[1] = 2; d[2] = 3;
        x = d;
        f[0] = 3; f[1] = 2; f[2] = 1;


        u = d*f;
        std::cout << u[0] << " " << u[1] << " " << u[2] << std::endl;

        u = d*x;
        std::cout << u[0] << " " << u[1] << " " << u[2] << std::endl;

        //u = s*d*x; //does not work

        std::cout << u[0] << " " << u[1] << " " << u[2] << std::endl;

        getchar();
        return 0;
    }


Share this post


Link to post
Share on other sites
BitMaster    8651
I strongly advise against doing thing like this in an implicit way, it can cause some truly horribly to locate precision problems.

Make it easy for a user to convert a VectorX from one underlying scalar type to another (explicit constructor, to<newScalar>() member function) but do not do anything based on implicit conversion. If things are of different underlying types, the compiler should scream at you. When you consider the conversion justified it should be easy and simple to explicitly request it. At the same time it should be obvious from the code that such a conversion happens. Edited by BitMaster

Share this post


Link to post
Share on other sites
Misery    354


Like

Posted Today, 09:21 AM
I strongly advise against doing thing like this in an implicit way, it can cause some truly horribly to locate precision problems

 

Thanks for your advise. I agree with you absolutely. However my approach allows to conversions only if such converions are explicitly defined by user or by the language standard. Therefore I find it nice approach. And in the case of lack of conversion function it prompts pretty error message instead of huge message. Therefore I care much about implementing it that way.

Edited by Misery

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this