2

I am looking for a way to express interoperability between a class A and built-in integer types while keeping high flexibility in my code. E.g, I would like to be able to use operator & freely between (A and A), (A and int), (int and A) and (int and int), i.e. I want to have the result of x = y & z whether x, y and z are type class A or type int, just writing:

x = y & z;

The following code works:

#include <cstdlib>
#include <iostream>

class A {
public:
    int x;

    explicit A(int i) : x(i) {}

    operator int() {
        return this->x;
    }

    A operator &(const A& src) const {
        return A(this->x & src.x);
    }

};

int main() {
    int b(2), b2(0), b3(0);
    A a(3);

    b2 = a & b;
    b3 = b & a;

    std::cout << b2 << std::endl;
    std::cout << b3 << std::endl;

    return 0;
}

However, if I add a new cast function from A to unsigned int in class A, this does not work any more because operator & is defined between (int and int) and also (int and unsigned int), so when I do:

b2 = a & b

the compiler doesn't know if a should be cast to int or unsigned int, which is logical. I see 2 possibilities to solve it:

  1. Explicitely implementing operator & between A and int and between int and A. I don't want that because adding the compatibility with another type would require to re-implement many combinations of all operators which need to be supported.
  2. Forcing implicit conversion from built-in types to A, so only operator & between A and A is required.

For flexibility and maintainability, solution 2 is much better I think. So I can implement the following class A instead:

class A {
public:
    int x;

    A(int i) : x(i) {}

    A(unsigned int i) : x(i) {}

    explicit operator int() {
        return this->x;
    }

    explicit operator unsigned int() {
        return static_cast<unsigned int>(this->x);
    }

};

A operator &(const A& src1, const A& src2) {
    return A(src1.x & src2.x);
}

Now, though conversions from/to int and unsigned int are both defined, I can perform whether (A and A), (A and int), (int and A) and (int and int).

However I can't compile the code:

b2 = a & b;
b3 = b & a;

Because as b2 and b3 are int and (a & b) (resp. (b & a)) return a A and cast from A to int must now be explicit, I have to write:

b2 = static_cast<int>(a & b);
b3 = static_cast<int>(b & a);

My question (finally) is:

Is there a way to code class A so I can do:

b2 = a & b;
b3 = b & a;

while keeping only one definition of operator &, between (A and A)? In theory, that could be done overloading operator =(const A&) of class int, which is technically impossible.

Benjamin Barrois
  • 2,566
  • 13
  • 30
  • I've not found this to be an easy pattern to accomplish in C++. The other developers using it tend to need to refer to the implementation when they want to use it, and become frustrated with it. It tends to get labelled "over engineered". If you are using it for just yourself and your own project, so the idiom works for you, then disregard my comment. – Eljay Mar 11 '18 at 19:10
  • @bipll Can you please be more precise to help me understand how it can solve my problem? Thanks! – Benjamin Barrois Mar 11 '18 at 19:10

2 Answers2

1

I think bipll means using free-standing operator& functions:

#include <cstdlib>
#include <iostream>

using std::cout;
using std::endl;

class A
{
  int x;
public:
  explicit A(int i) : x{i}
  { }

  explicit A(unsigned i) : x{static_cast<int>(i)}
  { }

  operator int() const
  {
    return this->x;
  }

  operator unsigned() const
  {
    return static_cast<unsigned>(this->x);
  }
};

A operator&(A const& lhs, A const& rhs)
{
  return A(lhs.operator int() & rhs.operator int());
}

A operator&(A const& lhs, int rhs)
{
  return A(lhs.operator int() & rhs);
}

A operator&(int lhs, A const& rhs)
{
  return A(lhs & rhs.operator int());
}

A operator&(A const& lhs, unsigned rhs)
{
  return A(lhs.operator unsigned() & rhs);
}

A operator&(unsigned lhs, A const& rhs)
{
  return A(lhs & rhs.operator unsigned());
}

int main()
{
  auto b = 2;
  auto b2 = 0;
  auto b3 = 0;
  auto u = 2;
  auto u4 = 0u;
  auto u5 = 0u;
  auto a = A{3};

  b2 = a & b;
  b3 = b & a;
  u4 = a & u;
  u5 = u & a;

  cout << b2 << endl;
  cout << b3 << endl;
  cout << u4 << endl;
  cout << u5 << endl;
}
Eljay
  • 4,648
  • 3
  • 16
  • 27
  • Ok, that's what I thought. However, this solution is obvious and discards flexibility brought by implementing only 1 operator &. In my code, I want to support many operators and interoperability with many built-in types, which represents hundreds of operators overloading with this solution. – Benjamin Barrois Mar 11 '18 at 19:21
  • Yes, that is the path I've been down too. That's where the "over engineered" feedback came from. If you can keep the use cases very narrow, you limit the amount of extra glue functions you need to create. – Eljay Mar 11 '18 at 19:22
0

I think I have just come across a solution. Please consider the following code:

class A {
public:
    int x;

    explicit A(int i) :
            x(i) {
    }

    explicit A(unsigned int i) :
            x(i) {
    }

    operator int() {
        return this->x;
    }

    operator unsigned int() {
        return static_cast<unsigned int>(this->x);
    }

};

template<typename T> A operator &(const A& src1, const T& src2) {
    return A(src1.x & src2);
}

template<typename T> A operator &(const T& src1, const A& src2) {
    return A(src1 & src2.x);
}

int main() {
    int b = 2, b2 = 0, b3 = 0;
    A a(3);

    b2 = a & b;
    b3 = b & a;

    std::cout << b2 << std::endl;
    std::cout << b3 << std::endl;

    return 0;
}

It just happens to work. The only problem I see (which is quite important though) is that you can't control the effects of operator & with built-in types you have not considered.

New question then: Is there a way to restrict my template operator & to a given list of types (without template specialization)?

Benjamin Barrois
  • 2,566
  • 13
  • 30