HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

C++ vector that uses expression templates technique to increase performance of mathematical expressions

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
expressiontechniqueexpressionsusestemplatesthatincreaseperformancevectormathematical

Problem

I've been reading Item 6 from Scott Meyers' Effective Modern C++ and noticed that he mentioned a technique called expression templates. I've decided to give it a try and implemented a vector that supports addition and subtraction:

#include 
#include 

template
class Vec
{
public:
    std::vector data;

    typedef typename std::vector::size_type size_type;

    Vec(size_type size): data(size)
    {
    }

    Vec(const std::initializer_list& elements): data(elements.size())
    {
        size_type i = 0;
        for (const auto& el: elements)
        {
            data[i++] = el;
        }
    }

    template
    Vec(const VecOperation& vo): data(vo.t2.data.size())
    {
        for (size_type i = 0; i 
struct VecSum
{
    const T1& t1;
    const T2& t2;
    auto operator[](typename T2::size_type i) const
    {
        return t1[i] + t2[i];
    }
};

template
struct VecDiff
{
    const T1& t1;
    const T2& t2;
    auto operator[](typename T2::size_type i) const
    {
        return t1[i] - t2[i];
    }
};

template
auto operator+(const T1& t1, const T2& t2)
{
    return VecSum{t1, t2};
}

template
auto operator-(const T1& t1, const T2& t2)
{
    return VecDiff{t1, t2};
}

int main()
{
    Vec v1{1, 2, 3, 4, 5};
    Vec v2{6, 7, 8, 9, 11};
    Vec v3{3, 5, 2, 0, 17};

    Vec v4 = v1+v2-v3;

    for (const auto& x: v4.data)
    {
        std::cout << x << ", ";
    }
    std::cout << std::endl;

    return 0;
}


The main advantage of this solution is that in the line Vec v4 = v1+v2-v3; no additional temporaries of type Vec are created, which increases performance.

I'd be grateful if someone could point potential drawbacks and possible improvements of this code.

Solution

Generally speaking, it's pretty good. However, there are still a few things that I would have done differently:

-
typedef becomes kind of ugly now that we have type aliases. Always using type aliases generally tends to produce more readable and more consistent code:

using size_type = typename std::vector::size_type;


-
Loop only when you have to. In your std::initializer_list constructor, you don't need to loop; you can use std::vector's constructor taking a pair of iterators instead:

Vec(const std::initializer_list& elements):
    data(std::begin(elements), std::end(elements))
{
}


-
By the way std::initializer_list tends to be no more than a pair of pointers. You can take it directly by value instead of taking it by const reference.

-
In operator+ and operator-, I would have placed the return type into the function signature and then used the list initialization syntax in the return statement:

template
auto operator+(const T1& t1, const T2& t2)
    -> VecSum
{
    return { t1, t2 };
}


Whether it is better or not is debatable but when the return type is fixed, rather meaningful and arguably simple, I prefer to expose it in the signature.

Code Snippets

using size_type = typename std::vector<T>::size_type;
Vec(const std::initializer_list<T>& elements):
    data(std::begin(elements), std::end(elements))
{
}
template<typename T1, typename T2>
auto operator+(const T1& t1, const T2& t2)
    -> VecSum<T1, T2>
{
    return { t1, t2 };
}

Context

StackExchange Code Review Q#97962, answer score: 6

Revisions (0)

No revisions yet.