Filipe Rodrigues
Filipe Rodrigues

Reputation: 2177

C++ Template<Operator>

I'm currently building a BigInt class and, when overloading the operators &, | and ^, which will all have similar function syntax, I wondered if it would be possible to template the operators themselves as such:

template<operator K> 
BigInt operator K( const BigInt& a, const BigInt& b )
{
 BigInt Result = 0;
 uint64_t Max = max(a.GetSize() , b.GetSize()) /* max(a,b) is defined outside */
 for (uint64_t n=0; n < Max; n++)
 {
  Result[n] = a[n] K b[N];
 }
 return Result;
}

Where A[n] returns a bool with the nth digit of A (binary), and apply this to the operators &, | and ^, this way I wouldn't write 3 operator overloads that are identical for the exception of 2 letters.

I know this syntax doesn't work, but I'm asking if there is any way to do what you might expect this syntax to do: substitute K by &, | or ^ and only those when you write (a & b) in the code.

If it helps here is my definition of the class:

class BigInt
{
private:
    vector<bool> num;

public:
    /* Constructors */
    BigInt();
    template<class T> BigInt(T);

    /* Class Methods */
    void RLZ(); /* Remove Leading Zeroes */
    uint64_t GetSize() const;
    void print();

    /* Operator Overloads */
    std::vector<bool>::reference operator[] (uint64_t);
    bool operator[] (uint64_t) const;
    BigInt& operator=(const BigInt&);
};

Upvotes: 2

Views: 209

Answers (2)

0x5453
0x5453

Reputation: 13589

One idea would be to define a helper function templated on the operator function (since you can't template on the operator itself), and then instantiate your template in the definitions of the appropriate BigInt operators. For example:

template <class OperatorFn> 
BigInt bitwiseOperator(const BigInt& a, const BigInt& b) const
{
    BigInt Result = 0;
    uint64_t Max = max(a.GetSize(), b.GetSize());
    for (uint64_t n=0; n < Max; n++)
    {
        Result[n] = OperatorFn{}(a[n], b[N]);
    }
    return Result;
}
BigInt operator&(const BigInt& a, const BigInt& b) const { return bitwiseOperator<std::bit_and<bool>>(a, b); }
BigInt operator|(const BigInt& a, const BigInt& b) const { return bitwiseOperator<std::bit_or<bool>>(a, b); }
BigInt operator^(const BigInt& a, const BigInt& b) const { return bitwiseOperator<std::bit_xor<bool>>(a, b); }

Upvotes: 7

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275300

I would write this:

template<class F>
BigInt& bitwiseReplace( F&& f, BigInt& lhs, BigInt const& rhs ) {
  auto max = (std::max)(lhs.GetSize(), rhs.GetSize());
  for (uint64_t i=0; i < max; ++i)
    lhs[i] = f( lhs[i], rhs[i] );
  return lhs;
}

note that I take lhs by reference and modify it. This is on purpose. This corresponds to &= and |=.

template<class F>
BigInt bitwiseCreate( F&& f, BigInt lhs, BigInt const& rhs ) {
  bitwiseReplace( std::forward<F>(f), lhs, rhs );
  return lhs;
}

this corresponds to + or |.

In your class:

BigInt& operator=(const BigInt&)=default;
BigInt& operator=(BigInt&&)=default;
BigInt(const BigInt&)=default;
BigInt(BigInt&&)=default;

have default move/copy assign/construct, as they do the right thing here.

Now you just have to write one line:

BigInt& operator&=( BigInt const& rhs ) { return bitwiseReplace( std::bit_and<bool>{}, *this, rhs ); }
BigInt& operator|=( BigInt const& rhs ) { return bitwiseReplace( std::bit_or<bool>{}, *this, rhs ); }

friend BigInt operator&( BigInt lhs, BigInt const& rhs ) { return bitwiseCreate( std::bit_and<bool>{}, std::move(lhs), rhs ); }
friend BigInt operator|( BigInt lhs, BigInt const& rhs ) { return bitwiseCreate( std::bit_or<bool>{}, std::move(lhs), rhs ); }

this requires a small amount of glue code.

The left hand side being taken by value means that

auto r = a | b | c;

is efficient -- the result of a | b is moved (well actually elided) into (a|b) | c with no copies performed.

Another advantage of this is that you can replace your std::vector<bool> with a std::vector<uint32_t>. Just write wordwiseReplace and wordwiseCreate that takes a function and operates on each uint32_t in a very similar way.

You can with a bit of additional work even implement + and - this way. The function object can carry the carry information. You do have to check if there is overflow/underflow at the end and adjust for it.

* and / will require more work.

Upvotes: 5

Related Questions