Benjamin Barrois
Benjamin Barrois

Reputation: 2686

C++ - Deal with implicit/explicit casts while keeping flexible code

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.

Upvotes: 2

Views: 58

Answers (2)

Benjamin Barrois
Benjamin Barrois

Reputation: 2686

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)?

Upvotes: 0

Eljay
Eljay

Reputation: 5321

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;
}

Upvotes: 1

Related Questions