0

Suppose I have a class A, and I define an overload of an operator, acting on a class of the standard library. To have a concrete example, see following code:

#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <vector>

template <typename T>
std::vector<T>& operator+=(std::vector<T>& lhs, const std::vector<T>& rhs) {
   if (lhs.size() != rhs.size())
        throw(std::runtime_error("incompatible vectors"));
   std::transform(rhs.begin(), rhs.end(), lhs.begin(), lhs.begin(), [](const auto& x, const auto& y) { return x + y; });
   return lhs;
}

template <typename T>
struct A {
   std::vector<T> v;
   A(const T& x) : v(3, x) { }
   void add (const std::vector<T>& rhs) { v += rhs; }
};

int main()
{
    A<int> a(1);
    std::vector<int> bvec(3, 2);
    a.add(bvec);

    return 0;
}

Check it live on Coliru.

The code works as expected. However, I would like to "hide" the overload of operator+=, such that it can be only used by struct A.

The reason is that, since operator += is a template one, I would need to insert it into a header, to be included whenever I need struct A in a compilation unit: this could potentially generates conflicts, for example if the compilation unit already has defined an operator+= for std::vector.

One way to "hide" it could be wrap everything inside a namespace:

namespace code {
template <typename T>
std::vector<T>& operator+=(std::vector<T>& lhs, const std::vector<T>& rhs) {

// ....

template <typename T>
struct A {

// ....
}

This seems a bit cumbersome, and again, if the code contains using namespace code, which I cannot prevent, it could still generate conflicts. Moreover, every other routines inserted inside the namespace code will be able to access to operator+=, again with potential conflicts.

Another solution could be to hide the operation inside the definition of struct A:

#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <vector>

template <typename T>
struct A {
private:
   template <typename U>
   void do_add(const std::vector<U>& rhs) {
   if (v.size() != rhs.size())
        throw(std::runtime_error("incompatible vectors"));
   std::transform(rhs.begin(), rhs.end(), v.begin(), v.begin(), [](const auto& x, const auto& y) { return x + y; });
}
public:
   std::vector<T> v;
   A(const T& x) : v(3, x) { }
   void add (const std::vector<T>& rhs) { do_add(rhs); }
};

int main()
{
    A<int> a(1);
    std::vector<int> bvec(3, 2);
    a.add(bvec);

    return 0;
}

(Obviously I could directly implement everything inside A::add, the above code is for illustration only-- in a real case several methods of A may need to call do_all, hence a separate private implementation).

The drawback of this solution is that it "ruins" a bit the readability of the code: now I have to call a routine do_add for doing something which is "logically" an operator+=.

Ideally, I would prefer to write something like

template <typename T>
struct A {
private:
    std::vector<T>& operator+=(std::vector<T>& lhs, const std::vector<T>& rhs);
// ...
}

that is, defining an operator+= inside the private part of struct A. In this way I would use the clear syntax v += rhs, and still restrict the use of operator+= to the code inside struct A. Alas, the code above cannot compile, because the compiler expects that operator+= inside struct A deals with objects of type A...

If the member v would be a user-defined type X, then I could indeed insert operator+= in the definition of X. But again, not for std classes. And, deriving from the std is UB.

Then, a third solution would be to wrap std::vector inside a custom class

template <typename T>
class vec_wrapper {
   vec_wrapper<T>& operator+=(const vec_wrapper& rhs);
   std::vector<T>& m_data;
   //...
}

But this is extremly cumbersome: If I then want to use my vec_wrapper as usual std::vector I would need to provide a duplicate of all std::vector member functions, say T& operator[](std::size_t n) { return m_data[n]; }, etc...

So, the question is: is there a way to mantain the "nicely" defined overload of operator+=, but restrict its use to struct A?

francesco
  • 7,189
  • 7
  • 22
  • 49
  • https://stackoverflow.com/questions/357404/why-are-unnamed-namespaces-used-and-what-are-their-benefits – Mat Jun 17 '23 at 09:46
  • 1
    Sometimes shortening the code just isn't worth it. Adding `static std::vector& increment(std::vector& lhs, const std::vector& rhs)` to use instead of the operator may even make the code simpler to understand... – fabian Jun 17 '23 at 18:00

1 Answers1

0

You may inspire by std::string operator+= and define the member operator.

template <typename T>
struct A {
   std::vector<T> v;
   A(const T& x) : v(3, x) { }
   void add (const std::vector<T>& rhs) { (*this) += rhs; }
   
    A<T>& operator+=(const std::vector<T>& rhs) {
       if (v.size() != rhs.size())
            throw(std::runtime_error("incompatible vectors"));
       std::transform(rhs.begin(), rhs.end(), v.begin(), v.begin(), [](const auto& x, const auto& y) { return x + y; });
       return *this;
    }
};

usage:

int main()
{
    A<int> a(1);
    std::vector<int> bvec(3, 2);
    a.add(bvec);
    
    a += std::vector<int>(3, 1);

    return 0;
}

Edit: If you want to preserve the operator in the way you declared it and just make it visible to the A class then just move the operator to A.cpp file and mark it as static. Static function is only visible in its object file.

// Visible only for this compilation unit (A.cpp)
template <typename T>
static std::vector<T>& operator+=(std::vector<T>& lhs, const std::vector<T>& rhs) 
{
   if (lhs.size() != rhs.size())
        throw(std::runtime_error("incompatible vectors"));
   std::transform(rhs.begin(), rhs.end(), lhs.begin(), lhs.begin(), [](const auto& x, const auto& y) { return x + y; });
   return lhs;
}
  • Thanks, but in an actual code I could more than 1 vector, and not necessarily a member field (it could be, say, a local variable)... – francesco Jun 17 '23 at 11:25
  • I mean that, for example, the class may hold ```std::vector v1, v2```, and in a routine you want to do sometimes ```v1 += v```, or ```v2 += v```, or even ```v1 += v2```. Or inside a member function, you have perhaps a local ```std::vector v_local```, and you want to do ```v_local += v```. I think your solution is not flexible enough to treat all cases... – francesco Jun 17 '23 at 11:56
  • @francesco That was my solution, not his. Also, you did not include those cases in your sample code and question. I edited the answer. I hope it solves your issue. – Ruh Roh Raggy Jun 17 '23 at 12:14