1
votes

I am creating a vector math library templated on the type and number of elements. There is a SizedVectorBase struct where the number of elements is a member variable to prevent code bloat from vector functions that differ only in the size of the loop (see Scott Meyers' Effective C++ Item 44). I want component-wise operator overloads that loop through each element like += and *= to be in the base class for the code bloat. However, since all Vectors derive from SizedVectorBase, I want to prevent those operations between vectors of different sizes.

template<typename T>
struct SizedVectorBase
{
  private:
    std::size_t size;
    T* pData;
  protected:
    SizedVectorBase<T>& operator+=(const SizedVectorBase<T>& rhs)
    {
      for(std::size_t i = 0; i < size; ++i)
      {
        pData[i] += rhs.pData[i];
      }
      return *this;
    }
};
template<typename T, std::size_t n>
struct Vector : public SizedVectorBase<T>
{
  std::array<T, n> data;

  Vector<T, n>& operator+=(const Vector<T, n>& rhs)
  {
    static_cast<SizedVectorBase<T>&>(*this) += static_cast<const SizedVectorBase<T>&>(rhs);
    return *this;
  }
};

I tried std::enable_if in SizedVectorBase, but it doesn't appear that you can access the size member of specific instances *this and rhs. I got compile errors like "error C2355: 'this': can only be referenced inside non-static member functions or non-static data member initializers" and "error C2065: 'rhs': undeclared identifier"

typename std::enable_if<this->size == rhs.size>::type
SizedVectorBase<T>& operator+=(const SizedVectorBase<T>& rhs)

I then tried to make the functions protected and call them in the derived class, but I get a compile warning that I cannot access the protected member:

error C2248: 'SizedVectorBase::operator +=': cannot access protected member declared in class 'SizedVectorBase' with [ T=int ]

How can I prevent += from working with different sized vectors while retaining the sized loop in SizedVectorBase?

2
"it doesn't appear that you can access the size member of specific instances *this and rhs" -- that's pretty unclear. Can you elaborate, like e.g. give an example? BTW: Isn't std::array assignable? Even if not, why not simply use std::copy? BTW2: T is ALL_UPPERCASE, which suggests it is a macro.Ulrich Eckhardt
"to prevent code bloat ", Here you have one function += by Vector anyway and that method indeed depend of both T and n.Jarod42
" but I get a compile warning" can you copy it ?Jarod42
@UlrichEckhardt I tried to do this: typename std::enable_if<this->size == rhs.size>::type SizedVectorBase<T>& operator +=(const SizedVectorBase<T>& rhs)Will
@Jarod42 1>error C2248: 'SizedVectorBase<T>::operator +=': cannot access protected member declared in class 'SizedVectorBase<T>' 1> with 1> [ 1> T=int 1> ]Will

2 Answers

2
votes

As far as I can tell you are already there: the operator+= of Vector<T,n> is only defined for Vector<T,n> arguments (where n is the same compile-time integer!), so it will not work if n differs between left-hand and right-hand operands.

Good old godbolt confirms that's the case: try changing one of the sizes in the arguments to test.

0
votes

I think that your approach is partially flawed. The point is, that this technique you try to use is a micro-optimization. However, at the same time, you have a redundant pointer and size storage, inherited via the baseclass, which have to be initialized and dereferenced. This isn't incorrect, but throwing away performance in order to gain it via small optimizations in a different place is IMHO wrong. First step of optimization is not doing unnecessary things. Only in the second step, you try to make things fast or small.

Take the following approach as alternative. I have left out parts of the template code (the element type parameter, replaced with a plain int) since it isn't really necessary to illustrate the problem. Also, I have elided much of the boilerplate code, the classes are just skeletons.

// operations on variable-size vectors
namespace vector_operations
{
    // Both `source` and `target` must point to a range
    // of at least `size` elements.
    void add(int* target, int const* source, size_t size);
}

// fixed-size vector
template<size_t size>
class vector
{
    int data[size];
public:
    vector<size>& operator+=(vector<size> const& rhs)
    {
        vector_operations::add(data, &rhs.data, size);
        return *this;
    }
};

Now, compare that to your approach:

  • vector_operations is stateless, so there are no redundant members. Instead, all required data is passed into the functions that operate on the data. This makes it very clear what is needed there. Note that there is no reason why operator+= has to be implemented on top of another operator+=!
  • You could convert the namespace into a baseclass that you inherit from privately. The only advantage would be in the way that you access members, i.e. you save some typing. Since it is empty, it could be optimized away completely. I kept this separate here to make it clearer.