1

Here is some context :

I need to overload some operators to act on std::vector operands. I decided against implementing directly the operator overloads in the global scope, as I believe it would be a bad practice due to possible conflicts. As a result, I decided to implement a templated class which :

  1. Behaves as much as possible like a std::vector
  2. Allows the overloading of the operator in a transparent way

The initial idea was to inherit from std::vector but I quickly realized it is not recommended. I therefore decided to create a class which holds a std::vector data member and implements an interface that makes it as transparent to the user as possible.

Here is a snippet of what I came up with :

template<typename T>
   class Vector {
   public:
       Vector() = default;
       ~Vector() = default;
                
       Vector(std::initializer_list<T> list) :data(list) {}

       template<typename... Args>
       Vector(Args... args) : data(args...){}

       
       /* All std::vector functions with parameters and return value will look like this*/
       template<typename...Args>
       auto reserve(Args... args) {
         return data.reserve(args...);
       }

       /* All std::vector functions with parameters and void return value will look like this*/
       template<typename... Args>
       void resize(Args... args) { 
         data.resize(args...);
       }

       /* All std::vector functions without parameter and void return value will look like this*/  
       void clear() {
         data.clear();
       }
      
       /* All std::vector functions with return value will look like this*/
       Vector& operator=(Vector const & other) {
         data = other.getData();
         return *this;
       }

       size_t size() const {
         return data.size();
       }

       std::vector<T> & getData() {
         return data;
       }
       std::vector<T> const & getData() const {
         return data;
       }
       
       T & operator[](size_t i) {
         return data[i];
       }

       T const & operator[](size_t i) const {
         return  data[i];
       }
       
       /* All std::vector functions without args and with return value will look like this*/
       auto begin() {
         return data.begin();
       }
   private:
     std::vector<T> data;
};

And the overloaded operators would be written like :

template <typename T>
void operator -= (Vector<T>& a, const Vector<T>& b) {
  if (a.size() != b.size()) {
    throw std::length_error("void operator -= (Vector<T>& a, const Vector<T>& b)\nBoth Vector<T> operands should have the same size");
  }

  for (int i = 0; i < a.size(); ++i) {
    a[i] -= b[i];
  }
}

My question is the following : how viable is this strategy ? Is there something inherently wrong with that ?

I do feel that there is quite a big amount of boilerplate code (all functions with arguments and with return value will look in a certain way, and only the name of the functions that will be passed to the data member will change), do you see a potential improvement possible there ?

Al_th
  • 1,174
  • 1
  • 12
  • 24
  • Sounds like you want a [`std::valarray`](https://en.cppreference.com/w/cpp/numeric/valarray) instead – NathanOliver Feb 15 '22 at 16:49
  • if you want to really forward, you have to use forwarding reference instead of passing by value: `template Vector(Args&&... args) : data(std::forward(args)...){}`. – Jarod42 Feb 15 '22 at 16:53
  • Wouldn't implement free functions operating on a vector parameter be enough? It would free you from all this forwarding code – Mickaël C. Guimarães Feb 15 '22 at 16:57
  • @NathanOliver : Thanks for pointing this data structure out. I was not aware of it. I guess the question still remains as it is a bit broader (imagine having to overload differently the operator rather than element wise standard operators) – Al_th Feb 15 '22 at 17:00
  • @Jarod42 : You are correct indeed – Al_th Feb 15 '22 at 17:01
  • @MickaëlC.Guimarães : What exactly do you mean ? I can always implement free functions/operator overloads at a global level but if they are not properly encapsulated then the risk is that someone else does the same and we end up with multiple operator overload definitions, isn't it ? Or maybe I did not understand you properly. – Al_th Feb 15 '22 at 17:01
  • @Al_th I will add an answer to be more clear :-) – Mickaël C. Guimarães Feb 15 '22 at 17:03
  • @Al_th: MickaëlC.Guimarães proposed **named** free function instead of operators. – Jarod42 Feb 15 '22 at 17:03

1 Answers1

0

The simplest and easiest way to achieve what you want is to implement a free function :

template <class T>
void minusEqual(std::vector<T>& target, const std::vector<T>& param)
{
  if (a.size() != b.size()) {
    throw std::length_error("void operator -= (Vector<T>& a, const Vector<T>& b)\nBoth Vector<T> operands should have the same size");
  }

  for (int i = 0; i < a.size(); ++i) {
    a[i] -= b[i];
  }
}

Of course, in order to prevent other contributors from duplicating it, you need to store it in a clear way so that they will know where to find it.

Although it is usually discouraged, you can also inherit from std::vector. This question and its answer will give you useful information about that.

Mickaël C. Guimarães
  • 1,020
  • 2
  • 14
  • 32