Search code examples
c++templatesvisual-studio-2015operator-overloadingfriend

Overloading Operators in Templates Classes with Friend Operators


Consider this following class Operand: It is a pretty straight forward class as it takes in a single Parameter and defines a bunch of overloaded operators and for simplicity of testing this class I chose to use a float type since there are division operators. If you notice with some of the operators they are defined as friends so that one is able to do this in their code:

Edit Someone asked in the comments what does

inline friend Operand Operand::operator+( const Operand& A, const Operand B ) 

mean? It is an overloaded operator but since one is already defined for the class itself as a member this is declared as a friend to the class and is defined within the class but is not a class member. This allows the user to add two Operand class instances or objects together and returns back the newly calculated value as an Operand object. I'll add comments to the snippet section as to what overloaded operators are being used.

snippet

Operand A( 2.3f );
Operand B( 4.5f );
Operand C( 0 );
Operand D( 0 );

C = A + B;     // inline friend Operand Operand::operator+( const Operand& A, const Operand B );
D = C + 2.5f;  // Overloaded Operator Defined as Class Member
D = 3.4f + B;  // inline friend Operand Operand::operator+( const Value& value, const Operand A );

I've done this with the 4 common arithmetic operations +-*/ where one operator is defined in the class as a member and 2 friend operators are defined for each of the 4 operations. Here is the class definition.

Operand.h

#ifndef OPERAND_H
#define OPERAND_H

class Operand {
public:
    static const float ZERO;

protected:
    float operand_;

public:
    explicit Operand( float a = float() ) : operand_( a ) {}

    inline float getOperand() const { 
        return operand_; 
    }


    inline Operand& operator+=( const float& value ) {
        operand_ += value;
        return *this;
    }

    inline Operand& operator-=( const float& value ) {
        operand_ -= value;
        return *this;
    }

    inline Operand& operator*=( const float& value ) {
        operand_ *= value;
        return *this;
    }

    inline Operand& operator/=( const float& value ) {
        if ( isZero( value ) ) {
            operand_ = 0;
        } else {
            operand_ /= value;
        }
    }

    inline Operand operator+() const { // Unary
        return *this; 
    }

    inline Operand operator+( const float& value ) const { 
        return Operand( operand_ + value ); 
    }

    inline Operand operator-() const { // Unary
        return Operand( -operand_ ); 
    }

    inline Operand operator-( const float& value ) const {
        return Operand( operand_ - value );
    }

    inline Operand operator*( const float& value ) const {
        return Operand( operand_ * value );
    }

    inline Operand operator/( const float& value ) const {
        if ( isZero( value ) ) {
            return Operand( 0 );
        } else {
            return Operand( operand_ / value );
        }
    }

    inline friend Operand Operand::operator+( const Operand& A, const Operand B ) {
        return Operand( A.getOperand() + B.getOperand() );
    }
    inline friend Operand Operand::operator+( const float& value, Operand A ) {
        return Operand( value + A.getOperand() );
    }

    inline friend Operand Operand::operator-( const Operand& A, const Operand B ) {
        return Operand( A.getOperand() - B.getOperand() );
    }
    inline friend Operand Operand::operator-( const float& value, Operand A ) {
        return Operand( value - A.getOperand() );
    }

    inline friend Operand Operand::operator*( const Operand& A, const Operand B ) {
        return Operand( A.getOperand() * B.getOperand() );
    }
    inline friend Operand Operand::operator*( const float& value, const Operand A ) {
        return Operand( value * A.getOperand() );
    }

    inline friend Operand Operand::operator/( const Operand& A, const Operand B ) {
        if ( isZero( B.getOperand() ) ) {
            return Operand( 0 ); 
        } else {
            return Operand( A.getOperand() / B.getOperand() );
        }
    }
    inline friend Operand Operand::operator/( const float& value, const Operand A ) {
        if ( isZero( A.getOperand() ) ) {
            return Operand( 0 );
        } else {
            return Operand( value / A.getOperand() );
        }
    }       

    inline static bool isZero( float value ) {
        if ( (value > -ZERO) && (value < ZERO) ) {
            return true;
        }
        return false;
    } // isZero

}; // Operand

// #include "Operand.inl"

#endif // OPERAND_H

Operand.cpp

#include "Operand.h"

const float Operand::ZERO = static_cast<float>(1e-7); 

What I want to do is to template this class.

So here is the same class only defined as a class template:

OperandT.h

#ifndef OPERAND_T_H
#define OPERAND_T_H

template <typename T>
class OperandT {
public:
    static const T ZERO;

protected:
    T operand_;

public:

    explicit OperandT<T>( T a = T() ) : operand_( a ) {}

    inline T getOperand() const { 
        return operand_; 
    } // getOperand 

    inline OperandT<T>& operator+=( const T& value ) {
        operand_ += value;
        return *this;
    } // operator+=

    inline OperandT<T>& operator-=( const T& value ) {
        operand_ -= value;
        return *this;
    } // operator-=

    inline OperandT<T>& operator*=( const T& value ) {
        operand_ *= value;
        return *this;
    } // operator*=

    inline OperandT<T>& operator/=( const T& value ) {
        if ( isZero( value ) ) {
            operand_ = 0;
        } else {
            operand_ /= value;
        }
    } // operator/=

    inline OperandT<T> operator+() const { 
        return *this; 
    } // operator+ Unary

    inline OperandT<T> operator+( const T& value ) const { 
        return OperandT<T>( operand_ + value ); 
    } // operator+ Binary

    inline OperandT<T> operator-() const {
        return OperandT<T>( -operand_ ); 
    } // operator- Unary (Negate Value)

    inline OperandT<T> operator-( const T& value ) const {
        return OperandT<T>( operand_ - value );
    } // opeator- Binary (Subtraction)

    inline OperandT<T> operator*( const T& value ) const {
        return OperandT<T>( operand_ * value );
    } // operator* Post Multiply

    inline OperandT<T> operator/( const T& value ) const {
        if ( isZero( value ) ) {
            return OperandT<T>( 0 );
        } else {
            return OperandT<T>( operand_ / value );
        }
    } // operator/ Post Divide

    /*/ Having Trouble With Operators When Using Templates and Friends
    inline friend OperandT<T> OperandT<T>::operator+( const OperandT<T>& A, const OperandT<T> B ) {
        return OperandT<T>( A.getOperand() + B.getOperand() );
    }
    inline friend OperandT<T> OperandT<T>::operator+( const float& value, OperandT<T> A ) {
        return OperandT<T>( value + A.getOperand() );
    }

    inline friend OperandT<T> OperandT<T>::operator-( const OperandT<T>& A, const OperandT<T> B ) {
        return OperandT<T>( A.getOperand() - B.getOperand() );
    }
    inline friend OperandT<T> OperandT<T>::operator-( const float& value, OperandT<T> A ) {
        return OperandT<T>( value - A.getOperand() );
    }

    inline friend OperandT<T> OperandT<T>::operator*( const OperandT<T>& A, const OperandT<T> B ) {
        return OperandT<T>( A.getOperand() * B.getOperand() );
    }
    inline friend OperandT<T> OperandT<T>::operator*( const float& value, const OperandT<T> A ) {
        return OperandT<T>( value * A.getOperand() );
    }

    inline friend OperandT<T> OperandT<T>::operator/( const OperandT<T>& A, const OperandT<T> B ) {
        if ( isZero( B.getOperand() ) ) {
            return OperandT<T>( 0 ); 
        } else {
            return OperandT<T>( A.getOperand() / B.getOperand() );
        }
    }
    inline friend OperandT<T> OperandT<T>::operator/( const float& value, const OperandT<T> A ) {
        if ( isZero( A.getOperand() ) ) {
            return OperandT<T>( 0 );
        } else {
            return OperandT<T>( value / A.getOperand() );
        }
    } */

    inline static bool isZero( T value ) {
        if ( (value > -ZERO) && (value < ZERO) ) {
            return true;
        }
        return false;
    } // isZero

}; // OperandT

#include "OperandT.inl"

#endif // OPERAND_T_H

OperandT.cpp

#include "OperandT.h"

template <typename T>
const T OperandT<T>::ZERO  = static_cast<T>( static_cast<float>(1e-7) );

With the friend operators commented out it compiles and the in class defined operators work but when I uncomment the lower section of the class I end up getting compiler errors.

The few questions that I have are:

  • Is there a valid way to defined friend operators or functions with class templates?
  • If so, what is the appropriate syntax due to the strangeness of templates?
  • If not, what are possible alternatives to achieve the same functionality to keep this as generic as possible?
  • Last but not least, why is it that the friends work perfectly fine in the first class, but once you add the concept of templates, you end up having a train wreck?

Solution

  • After having a brief conversation with Danh in the comment section He has brought to my attention that the way I was able to implement the overloaded friend operators in visual studio 2013 - 15 should be considered a bug. Yes it will compile and run without error on visual studio as well as this website for visual studio compilers rextesters.com. He has stated that the proper way to implement overloaded friend operators is as such:

    The proper way is friend Operand operator+(Operand A, Operand B) or friend Operand operator+(const Operand& A, const Operand& B), I'm going to file a bug to MSFT – Danh

    He also showed me where it failed to compile under GCC or Clang from here: http://melpon.org

    I told him I would take this into consideration and I did. So I updated my Operand header file accordingly and it compiles, builds and runs correctly. Then I went ahead and fixed the template version and it is working as well.

    Here are the updated class headers.

    Operand.h

    #ifndef OPERAND_H
    #define OPERAND_H
    
    class Operand {
    public:
        static const float ZERO;
    
    protected:
        float operand_;
    
    public:
        explicit Operand( float a = float() ) : operand_( a ) {}
    
        inline float getOperand() const { 
            return operand_; 
        }
    
    
        inline Operand& operator+=( const float& value ) {
            operand_ += value;
            return *this;
        }
    
        inline Operand& operator-=( const float& value ) {
            operand_ -= value;
            return *this;
        }
    
        inline Operand& operator*=( const float& value ) {
            operand_ *= value;
            return *this;
        }
    
        inline Operand& operator/=( const float& value ) {
            if ( isZero( value ) ) {
                operand_ = 0;
            } else {
                operand_ /= value;
            }
        }
    
        inline Operand operator+() const { // Unary
            return *this; 
        }
    
        inline Operand operator+( const float& value ) const { 
            return Operand( operand_ + value ); 
        }
    
        inline Operand operator-() const { // Unary
            return Operand( -operand_ ); 
        }
    
        inline Operand operator-( const float& value ) const {
            return Operand( operand_ - value );
        }
    
        inline Operand operator*( const float& value ) const {
            return Operand( operand_ * value );
        }
    
        inline Operand operator/( const float& value ) const {
            if ( isZero( value ) ) {
                return Operand( 0 );
            } else {
                return Operand( operand_ / value );
            }
        }
    
        inline friend Operand operator+( const Operand& A, const Operand& B ) {
            return Operand( A.getOperand() + B.getOperand() );
        }
        inline friend Operand operator+( const float& value, const Operand& A ) {
            return Operand( value + A.getOperand() );
        }
    
        inline friend Operand operator-( const Operand& A, const Operand& B ) {
            return Operand( A.getOperand() - B.getOperand() );
        }
        inline friend Operand operator-( const float& value, const Operand& A ) {
            return Operand( value - A.getOperand() );
        }
    
        inline friend Operand operator*( const Operand& A, const Operand& B ) {
            return Operand( A.getOperand() * B.getOperand() );
        }
        inline friend Operand operator*( const float& value, const Operand& A ) {
            return Operand( value * A.getOperand() );
        }
    
        inline friend Operand operator/( const Operand& A, const Operand& B ) {
            if ( isZero( B.getOperand() ) ) {
                return Operand( 0 ); 
            } else {
                return Operand( A.getOperand() / B.getOperand() );
            }
        }
        inline friend Operand operator/( const float& value, const Operand& A ) {
            if ( isZero( A.getOperand() ) ) {
                return Operand( 0 );
            } else {
                return Operand( value / A.getOperand() );
            }
        }       
    
        inline static bool isZero( float value ) {
            if ( (value > -ZERO) && (value < ZERO) ) {
                return true;
            }
            return false;
        }    
    }; // Operand
    
    //#include "Operand.inl"
    
    #endif // OPERAND_H
    

    OperandT.h

    #ifndef OPERAND_T_H
    #define OPERAND_T_H
    
    template <typename T>
    class OperandT {
    public:
        static const T ZERO;
    
    protected:
        T operand_;
    
    public:    
        explicit OperandT<T>( T a = T() ) : operand_( a ) {}
    
        inline T getOperand() const { 
            return operand_; 
        } 
    
        inline OperandT<T>& operator+=( const T& value ) {
            operand_ += value;
            return *this;
        } 
    
        inline OperandT<T>& operator-=( const T& value ) {
            operand_ -= value;
            return *this;
        } 
    
        inline OperandT<T>& operator*=( const T& value ) {
            operand_ *= value;
            return *this;
        } 
    
        inline OperandT<T>& operator/=( const T& value ) {
            if ( isZero( value ) ) {
                operand_ = 0;
            } else {
                operand_ /= value;
            }
        } 
    
        inline OperandT<T> operator+() const { 
            return *this; 
        } 
    
        inline OperandT<T> operator+( const T& value ) const { 
            return OperandT<T>( operand_ + value ); 
        } 
    
        inline OperandT<T> operator-() const {
            return OperandT<T>( -operand_ ); 
        } 
    
        inline OperandT<T> operator-( const T& value ) const {
            return OperandT<T>( operand_ - value );
        }
    
        inline OperandT<T> operator*( const T& value ) const {
            return OperandT<T>( operand_ * value );
        }
    
        inline OperandT<T> operator/( const T& value ) const {
            if ( isZero( value ) ) {
                return OperandT<T>( 0 );
            } else {
                return OperandT<T>( operand_ / value );
            }
        }
    
        inline friend OperandT<T> operator+( const OperandT<T>& A, const OperandT<T>& B ) {
            return OperandT<T>( A.getOperand() + B.getOperand() );
        }
        inline friend OperandT<T> operator+( const float& value, const OperandT<T>& A ) {
            return OperandT<T>( value + A.getOperand() );
        }
    
        inline friend OperandT<T> operator-( const OperandT<T>& A, const OperandT<T>& B ) {
            return OperandT<T>( A.getOperand() - B.getOperand() );
        }
        inline friend OperandT<T> operator-( const float& value, const OperandT<T>& A ) {
            return OperandT<T>( value - A.getOperand() );
        }
    
        inline friend OperandT<T> operator*( const OperandT<T>& A, const OperandT<T>& B ) {
            return OperandT<T>( A.getOperand() * B.getOperand() );
        }
        inline friend OperandT<T> operator*( const float& value, const OperandT<T>& A ) {
            return OperandT<T>( value * A.getOperand() );
        }
    
        inline friend OperandT<T> operator/( const OperandT<T>& A, const OperandT<T>& B ) {
            if ( isZero( B.getOperand() ) ) {
                return OperandT<T>( 0 ); 
            } else {
                return OperandT<T>( A.getOperand() / B.getOperand() );
            }
        }
        inline friend OperandT<T> operator/( const float& value, const OperandT<T>& A ) {
            if ( isZero( A.getOperand() ) ) {
                return OperandT<T>( 0 );
            } else {
                return OperandT<T>( value / A.getOperand() );
            }
        }
    
        inline static bool isZero( T value ) {
            if ( (value > -ZERO) && (value < ZERO) ) {
                return true;
            }
            return false;
        }
    }; // OperandT
    
    //#include "OperandT.inl"
    
    #endif // OPERAND_T_H
    

    Now my code is working properly using templates all thanks to Danh!