views:

406

answers:

5

I wrote an abstraction class for a math object, and defined all of the operators. While using it, I came across:

Fixed f1 = 5.0f - f3;

I have only two subtraction operators defined:

inline const Fixed operator - () const;
inline const Fixed operator - (float f) const;

I get what is wrong here - addition is swappable (1 + 2 == 2 + 1) while subtraction is not (same goes for multiplication and division). I immediately wrote a function outside my class like this:

static inline const Fixed operator - (float f, const Fixed &fp);

But then I realized this cannot be done, because to do that I would have to touch the class's privates, which results to using the keyword friend which I loath, as well as polluting the namespace with a 'static' unnecessary function.

Moving the function inside the class definition yields this error in gcc-4.3:

error: ‘static const Fixed Fixed::operator-(float, const Fixed&)’ must be either a non-static member function or a non-member function

Doing as GCC suggested, and making it a non-static function results the following error:

error: ‘const Fixed Fixed::operator-(float, const Fixed&)’ must take either zero or one argument

Why can't I define the same operator inside the class definition? if there's no way to do it, is there anyway else not using the friend keyword?

Same question goes for division, as it suffers from the same problem.

A: 

What's wrong with friend? That's the standard way of doing things.

Put it in the class definition, so you don't pollute anything.

GMan
+1  A: 
  1. "That's what friends are for..."
  2. You could add an implicit conversion between float and your type (e.g. with a constructor accepting float)... but I do think using a friend is better.
Assaf Lavie
2 won't really do the job - it will let you do "Fixed(0.5f) - f3", but implict conversion never happens to the lhs of an operator implemented as a member function. In effect, "this" is always what the caller supplies, not some temporary.
Steve Jessop
I guess (not sure) the reason is that you'd have a whole new dimension of name resolution if you could convert the lhs to any type that had that operator, resulting in too many ambiguities for the taste of the standards committee.
Steve Jessop
+3  A: 

If you need reassuring that friend functions can be OK:

http://www.gotw.ca/gotw/084.htm

Which operations need access to internal data we would otherwise have to grant via friendship? These should normally be members. (There are some rare exceptions such as operations needing conversions on their left-hand arguments and some like operator<<() whose signatures don't allow the *this reference to be their first parameters; even these can normally be nonfriends implemented in terms of (possibly virtual) members, but sometimes doing that is merely an exercise in contortionism and they're best and naturally expressed as friends.)

You are in the "operations needing conversions on the left-hand arguments" camp. If you don't want a friend, and assuming you have a non-explicit float constructor for Fixed, you can implement it as:

static inline Fixed operator-(const Fixed &lhs, const Fixed &rhs) {
    return lhs.minus(rhs);
}

then implement minus as a public member function, that most users won't bother with because they prefer the operator.

I assume if you have operator-(float) then you have operator+(float), so if you don't have the conversion operator, you could go with:

static inline Fixed operator-(float lhs, const Fixed &rhs) {
    return (-rhs) + lhs;
    // return (-rhs) -(-lhs); if no operator+...
}

Or just Fixed(lhs) - rhs if you have an explicit float constructor. Those may or may not be as efficient as your friend implementation.

Unfortunately the language is not going to bend over backwards to accommodate those who happen to loathe one of its keywords, so operators can't be static member functions and get the effects of friendship that way ;-p

Steve Jessop
A: 

When you define something like this,

inline const Fixed operator - (float f) const;

you are saying that I want this operator(you are inside the class) to operate on a specific type, float here for example.

Whereas a friend binary operator, means an operation between two types.

class Fixed
{
    inline friend const Fixed operator-(const Fixed& first, const float& second);
};

inline const Fixed operator-(const Fixed& first, const float& second)
{
    // Your definition here.
}

with friend operators you can have your class on either side of the operator it self.

AraK
A: 

In general, free function operators for arithmetic operations are better than implementing member functions. The main reason is the problem you are facing now. The compiler will treat the left and right sides differently. Note that while strict OO followers will consider only those methods inside the class curly braces part of its interface, but it has been argued by experts that not to be the case in C++.

If the free function operator requires access to private members, make the operator friend. After all if it is provided in the same header file (following Sutter's rationale above) then it is part of the class.

If you really want to avoid it and don't mind making your code less idiomatic (and thus less maintainable) you can provide a public method that does the real work and dispatch to that method from the operator.

class Fixed {
private:
   Fixed();
   Fixed( double d ); // implicit conversion to Fixed from double

   Fixed substract( Fixed const & rhs ) const;
// ...
};

Fixed operator-( Fixed const & lhs, Fixed const & rhs )
{
   return lhs.substract( rhs );
}

In the code above, you can substract Fixed - Fixed, Fixed - double, double - Fixed. The compiler will find the free function and implicitly convert (in the example through the double constructor) the doubles into Fixed objects.

While this is unidiomatic for arithmetic operators, it is close to the idiomatic way of proving a polymorphic dump operator. So while not being the most natural solution it won't be the most surprising code around either

// idiomatic polymorphic dump operator
class Base {
public:
   virtual std::ostream& dump( std::ostream & ) const;
};
std::ostream& operator<<( std::ostream& o, Base const & d )
{
   return d.dump( o );
}
David Rodríguez - dribeas