Шаблон выражения с CRTP в качестве lvalue

Я пишу библиотеку, которая использует шаблоны выражений с CRTP. Исходные файлы можно найти здесь: https://github.com/mspraggs/pyQCD/tree/master/lib/include/base

Шаблоны выражений основаны на примере, приведенном в статье Википедии на эту тему. Я перечислю код здесь на случай, если статья Wiki изменится в будущем:

#include <vector>
#include <cassert>

template <typename E>
// A CRTP base class for Vecs with a size and indexing:
class VecExpression {
public:
  typedef std::vector<double>         container_type;
  typedef container_type::size_type   size_type;
  typedef container_type::value_type  value_type;
  typedef container_type::reference   reference;

  size_type  size()                  const { return static_cast<E const&>(*this).size(); }
  value_type operator[](size_type i) const { return static_cast<E const&>(*this)[i];     }

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

// The actual Vec class:
class Vec : public VecExpression<Vec> {
  container_type _data;
public:
  reference  operator[](size_type i)       { return _data[i]; }
  value_type operator[](size_type i) const { return _data[i]; }
  size_type  size()                  const { return _data.size(); }

  Vec(size_type n) : _data(n) {} // Construct a given size:

  // Construct from any VecExpression:
  template <typename E>
  Vec(VecExpression<E> const& vec) {
    E const& v = vec;
    _data.resize(v.size());
    for (size_type i = 0; i != v.size(); ++i) {
      _data[i] = v[i];
    }
  }
};

template <typename E1, typename E2>
class VecDifference : public VecExpression<VecDifference<E1, E2> > {
  E1 const& _u;
  E2 const& _v;
public:
  typedef Vec::size_type size_type;
  typedef Vec::value_type value_type;
  VecDifference(VecExpression<E1> const& u, VecExpression<E2> const& v) : _u(u), _v(v) {
    assert(u.size() == v.size());
  }
  size_type size() const { return _v.size(); }
  value_type operator[](Vec::size_type i) const { return _u[i] - _v[i]; }
};

template <typename E>
class VecScaled : public VecExpression<VecScaled<E> > {
  double _alpha; 
  E const& _v;
public:
  VecScaled(double alpha, VecExpression<E> const& v) : _alpha(alpha), _v(v) {}
  Vec::size_type size() const { return _v.size(); }
  Vec::value_type operator[](Vec::size_type i) const { return _alpha * _v[i]; }
};

// Now we can overload operators:

template <typename E1, typename E2>
VecDifference<E1,E2> const
operator-(VecExpression<E1> const& u, VecExpression<E2> const& v) {
  return VecDifference<E1,E2>(u,v);
}

template <typename E>
VecScaled<E> const
operator*(double alpha, VecExpression<E> const& v) {
  return VecScaled<E>(alpha,v);
}

Что я хочу сделать, это добавить еще один шаблон выражения, который позволяет присваивать часть исходного объекта шаблона (класс Vec в приведенном выше коде и класс LatticeBase в коде, с которым я связан). Возможное использование:

Vec myvector(10);
Vec another_vector(5);
myvector.head(5) = another_vector; // Assign first 5 elements on myvector
myvector.head(2) = another_vector.head(2); // EDIT

Поэтому я бы создал новую функцию Vec::head, которая будет возвращать шаблон выражения для части объекта Vec. Я не знаю, как это вписалось бы в рамки, которые у меня сейчас есть. В частности, у меня есть следующие вопросы / комментарии:

  1. Я видел примеры того, чего я хочу достичь в шаблонах выражений, которые не используют CRTP. Что я получу, используя CRTP в этом случае? Есть ли смысл? Должен ли я бросить это и следовать другим примерам, которые я нашел?
  2. В текущей среде присваивание члену _data в классе Vec обрабатывается конструктором копирования в классе Vec. Это не сработает, если я захочу использовать шаблон выражения, возвращаемый Vec::head, поскольку присваивание происходит внутри класса, который содержит данные, а не шаблона выражения.
  3. Я попытался создать оператор присваивания в новом шаблоне выражения, но это не сработает с приведенным выше кодом, поскольку все члены шаблона выражения являются ссылками const, и поэтому оператор присваивания удаляется во время компиляции. Могу ли я просто переключать элементы в значения, а не в ссылки? Повлияет ли это на производительность, если потребуется дополнительное хранилище? Будет ли это работать (если я изменю сохраненную копию выражения, а не само выражение)?

В целом, я не совсем понимаю, как добавить шаблон выражения, который можно использовать в качестве lvalue в приведенном выше коде. Любое руководство по этому вопросу будет принята с благодарностью.

1 ответ

Решение

Попробуй это:

#include <vector>
#include <cassert>

template <typename E>
// A CRTP base class for Vecs with a size and indexing:
class VecExpression {
public:
    typedef std::vector<double>         container_type;
    typedef container_type::size_type   size_type;
    typedef container_type::value_type  value_type;
    typedef container_type::reference   reference;

    size_type  size()                  const { return static_cast<E const&>(*this).size(); }
    value_type operator[](size_type i) const { return static_cast<E const&>(*this)[i]; }

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

class VecHead;

// The actual Vec class:
class Vec : public VecExpression<Vec> {
    container_type _data;
public:
    reference  operator[](size_type i)       { return _data[i]; }
    value_type operator[](size_type i) const { return _data[i]; }
    size_type  size()                  const { return _data.size(); }

    Vec(size_type n) : _data(n) {} // Construct a given size:

    // Construct from any VecExpression:
    template <typename E>
    Vec(VecExpression<E> const& vec) {
        E const& v = vec;
        _data.resize(v.size());
        for (size_type i = 0; i != v.size(); ++i) {
            _data[i] = v[i];
        }
    }

    VecHead head(size_type s);
};

class VecHead : public VecExpression< VecHead >
{
    Vec::size_type _s;
    Vec& _e;
public:

    typedef Vec::size_type size_type;
    typedef Vec::value_type value_type;
    VecHead(std::size_t s, Vec& e)
        : _s(s)
        , _e(e)
    {
        assert(_e.size() >= _s);
    }

    size_type size() const { return _s; }
    value_type operator[](Vec::size_type i) const { assert(i < _s);  return _e[i]; }

    VecHead& operator = (const VecHead& rhs)
    {
        return operator=(static_cast<const VecExpression<VecHead>&>(rhs));
    }

    template <typename E>
    VecHead& operator = (const VecExpression<E>& rhs)
    {
        assert(rhs.size() >= _s);
        for (size_type i = 0; i < _s && i < rhs.size(); ++i)
            _e[i] = rhs[i];
        return *this;
    }
};

VecHead Vec::head(size_type s)
{
    VecHead aHead(s, *this);
    return aHead;
}

template <typename E1, typename E2>
class VecDifference : public VecExpression<VecDifference<E1, E2> > {
    E1 const& _u;
    E2 const& _v;
public:
    typedef Vec::size_type size_type;
    typedef Vec::value_type value_type;
    VecDifference(VecExpression<E1> const& u, VecExpression<E2> const& v) : _u(u), _v(v) {
        assert(u.size() == v.size());
    }
    size_type size() const { return _v.size(); }
    value_type operator[](Vec::size_type i) const { return _u[i] - _v[i]; }
};

template <typename E>
class VecScaled : public VecExpression<VecScaled<E> > {
    double _alpha;
    E const& _v;
public:
    VecScaled(double alpha, VecExpression<E> const& v) : _alpha(alpha), _v(v) {}
    Vec::size_type size() const { return _v.size(); }
    Vec::value_type operator[](Vec::size_type i) const { return _alpha * _v[i]; }
};

// Now we can overload operators:

template <typename E1, typename E2>
VecDifference<E1, E2> const
    operator-(VecExpression<E1> const& u, VecExpression<E2> const& v) {
        return VecDifference<E1, E2>(u, v);
}

template <typename E>
VecScaled<E> const
    operator*(double alpha, VecExpression<E> const& v) {
        return VecScaled<E>(alpha, v);
}

int main()
{
    Vec myvector(10);
    Vec another_vector(5);
    for (int i = 0; i < 5; ++i)
        another_vector[i] = i;

    myvector.head(5) = another_vector; // Assign first 5 elements on myvector
    assert(myvector.head(5).size() == 5);
    for (int i = 0; i < 10; ++i)
    {
        assert(myvector[i] == (i < 5 ? static_cast<double>(i) : 0.));
    }

    //! Added test due to comment vec1.head(2) = vec2.head(2) doesn't work.
    Vec vec1(10), vec2(10);
    for (int i = 0; i < 10; ++i)
        vec2[i] = 2 * (vec1[i] = i);

    vec1.head(2) = vec2.head(2);
    for (int i = 0; i < 10; ++i)
    {
        if (i < 2)
        {
            assert(vec1[i] == vec2[i]);
        }
        else
        {
            assert(vec1[i] != vec2[i]);
        }
    }

    return 0;
}
Другие вопросы по тегам