why should the function defined in class


H

hpsoar

Item 46: Define non-member functions inside templates when type
conversions are desired
Item 24 explains why only non-member functions are eligible for
implicit type conversions on all arguments, and it uses as an example
the operator* function for a Rational class. I recommend you
familiarize yourself with that example before continuing, because this
Item extends the discussion with a seemingly innocuous modification to
Item 24's example: it templatizes both Rational and operator*:


template<typename T>

class Rational {

public:

Rational(const T& numerator = 0, // see Item 20 for why params

const T& denominator = 1); // are now passed by reference



const T numerator() const; // see Item 28 for why return

const T denominator() const; // values are still passed by
value,

... // Item 3 for why they're const

};



template<typename T>

const Rational<T> operator*(const Rational<T>& lhs,

const Rational<T>& rhs)

{ ... }




As in Item 24, we want to support mixed-mode arithmetic, so we want
the code below to compile. We expect that it will, because we're using
the same code that works in Item 24. The only difference is that
Rational and operator* are now templates:


Rational<int> oneHalf(1, 2); // this example is from Item 24,

// except Rational is now a
template



Rational<int> result = oneHalf * 2; // error! won't compile




The fact that this fails to compile suggests that there's something
about the templatized Rational that's different from the non-template
version, and indeed there is. In Item 24, compilers know what function
we're trying to call (operator* taking two Rationals), but here,
compilers do not know which function we want to call. Instead, they're
trying to figure out what function to instantiate (i.e., create) from
the template named operator*. They know that they're supposed to
instantiate some function named operator* taking two parameters of
type Rational<T>, but in order to do the instantiation, they have to
figure out what T is. The problem is, they can't.

In attempting to deduce T, they look at the types of the arguments
being passed in the call to operator*. In this case, those types are
Rational<int> (the type of oneHalf) and int (the type of 2). Each
parameter is considered separately.

The deduction using oneHalf is easy. operator*'s first parameter is
declared to be of type Rational<T>, and the first argument passed to
operator* (oneHalf) is of type Rational<int>, so T must be int.
Unfortunately, the deduction for the other parameter is not so simple.
operator*'s second parameter is declared to be of type Rational<T>,
but the second argument passed to operator* (2) is of type int. How
are compilers to figure out what T is in this case? You might expect
them to use Rational<int>'s non-explicit constructor to convert 2 into
a Rational<int>, thus allowing them to deduce that T is int, but they
don't do that. They don't, because implicit type conversion functions
are never considered during template argument deduction. Never. Such
conversions are used during function calls, yes, but before you can
call a function, you have to know which functions exist. In order to
know that, you have to deduce parameter types for the relevant
function templates (so that you can instantiate the appropriate
functions). But implicit type conversion via constructor calls is not
considered during template argument deduction. Item 24 involves no
templates, so template argument deduction is not an issue. Now that
we're in the template part of C++ (see Item 1), it's the primary
issue.

We can relieve compilers of the challenge of template argument
deduction by taking advantage of the fact that a friend declaration in
a template class can refer to a specific function. That means the
class Rational<T> can declare operator* for Rational<T> as a friend
function. Class templates don't depend on template argument deduction
(that process applies only to function templates), so T is always
known at the time the class Rational<T> is instantiated. That makes it
easy for the Rational<T> class to declare the appropriate operator*
function as a friend:


template<typename T>

class Rational {

public:

...

friend // declare
operator*

const Rational operator*(const Rational& lhs, // function (see

const Rational& rhs); // below for
details)

};


this is a example in Effectvie C++ 3e, it is said that the function
declared as a friend should also be defined in class Rational. It
proves to be true. But I really didn't understand the explanation :If
we declare a function ourselves (which is what we're doing inside the
Rational template), we're also responsible for defining that function.
Can anyone explain it more clear? thank you
 
Ad

Advertisements

J

James Kanze

Item 46: Define non-member functions inside templates when type
conversions are desired

Just a note, but it would help if you'd have cited the
reference, so we'd know what book to look up Item 46 in.
Item 24 explains why only non-member functions are eligible
for implicit type conversions on all arguments, and it uses as
an example the operator* function for a Rational class. I
recommend you familiarize yourself with that example before
continuing, because this Item extends the discussion with a
seemingly innocuous modification to Item 24's example: it
templatizes both Rational and operator*:
template<typename T>
class Rational {
public:
Rational(const T& numerator = 0, // see Item 20 for why params
const T& denominator = 1); // are now passed by reference
const T numerator() const; // see Item 28 for why return
const T denominator() const; // values are still passed by value,
... // Item 3 for why they're const
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs)
{ ... }
As in Item 24, we want to support mixed-mode arithmetic, so we
want the code below to compile. We expect that it will,
because we're using the same code that works in Item 24. The
only difference is that Rational and operator* are now
templates:
Rational<int> oneHalf(1, 2); // this example is from Item 24,
// except Rational is now a template
Rational<int> result = oneHalf * 2; // error! won't compile

[explination of difference between template type deduction
and operator overload resolution deleted...]
template<typename T>
class Rational {
public:
...
friend // declare operator*
const Rational operator*(const Rational& lhs, // function (see
const Rational& rhs); // below for details)
};
this is a example in Effectvie C++ 3e, it is said that the
function declared as a friend should also be defined in class
Rational.

Not only should be, but almost must be. There's no way of
defining it later.
It proves to be true. But I really didn't understand the
explanation :If we declare a function ourselves (which is what
we're doing inside the Rational template), we're also
responsible for defining that function. Can anyone explain it
more clear?

The difference is simple. The friend function, above, is NOT a
template. Each instantiation of Rational declares a different
non-template friend, e.g.:

Rational< int > const operator*( Rational< int > const&,
Rational< int > const& ) ;
Rational< double > const operator*( Rational< double > const&,
Rational< double > const& ) ;

etc. So:
1. You need a separate implementation for each of these
functions. If you provide it in the class template, the
compiler will generate it automatically each time it is
needed. Otherwise, you have to write it, for each
instantiation of the template (which, of course, isn't
really feasable, since you don't know what types will be
used to instantiate the template).
2. Since it isn't a template, type deduction doesn't apply;
only operator overload resolution (which allows implicit
conversions).
3. The compiler finds the operator by ADL; that is: one of the
arguments must be a Rational<T> (for some T), in which case,
the compiler looks in Rational<T>, class T (if T is a class
type) and any namespaces which contain Rational or T (if T
is a user defined type). Note that it does NOT look in
Rational; as far as the compiler is concerned, at this
point, Rational doesn't exist, only instantiations of
Rational.

The usual way of doing this is to define a member *=, and use a
templated base class to provide the binary operators, e.g.:

template< typename DerivedType >
struct ArithmeticOperators
{
// ...
friend DerivedType operator*(
DerivedType const& lhs,
DerivedType const& rhs )
{
DerivedType result( lhs ) ;
result *= rhs ;
return result ;
}
// No actual members...
} ;

, then

template< typename T >
class Rational : public ArithmeticOperators< Rational< T > >
{
// ...
Rational& operator*=( Rational const& other ) ;
// ...
} ;

The <op>= operators are normally members, so you don't have to
worry about defining them inline.
 
D

DerTopper

On Mar 10, 2:44 pm, hpsoar <[email protected]> wrote:

[Snipped question and answer about friend declaration of operator in a
template class in order to allow for argument conversion.]

Thanks for this thorough explanation. Sometimes it pays to browse
through this newsgroup just to get a bit wiser :)

Stuart
 
H

hpsoar

On Mar 10, 2:44 pm, hpsoar <[email protected]> wrote:

[Snipped question and answer about friend declaration of operator in a
template class in order to allow for argument conversion.]

Thanks for this thorough explanation. Sometimes it pays to browse
through this newsgroup just to get a bit wiser :)

Stuart

so, what if I difined that friend as a template function?
 
Ad

Advertisements

J

James Kanze

On 3月11æ—¥, 下åˆ5æ—¶51分, (e-mail address removed) wrote:
so, what if I difined that friend as a template function?

There are three possibilities, I think:

You can declare a non-template function as friend:

If it is the same function for every specialization (i.e. the
function doesn't depend on the template parameters), this works
exactly like a friend in a non-template class. If the function
depends on any of the template arguments, you'll almost always
want to provide the implementation of the function in the class
template definition.

You can declare a function template as friend. In that case,
all of the specializations of the function template are friends
of all of the specializations of the class template; in this
case, operator*< Rational< int > > would be a friend of
Rational< double >, for example. In this case, I think you have
to declare the function template before declaring the class
template (but I'm far from sure---I've never actually done
this).

I think that there's also a possibility of declaring an instance
of a function template as a friend. Again, you need to have
declared the function template before hand. And to tell the
truth, I'm not to sure of the syntax; I think it varied some
before standardization, and I seem to recall some compilers
handling this in a non standard way in the past. But again, I'm
far from sure; the first solution works well for me, and I've
never been motivated to study the details for the other two.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top