Mombu the Programming Forum

Go Back   Mombu the Programming Forum > Programming > Mixed-type arithmetic with templatized classes
User Name
Password
REGISTER NOW! Mark Forums Read




Reply
1 26th October 09:01
scott meyers
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes



I hate it when I find out that I don't understand things I thought I
understood.

Suppose we have a class Rational and we want to support mixed-type
multiplication between Rationals and ints. To get the necesary implicit
type conversions, we declare operator* a non-member friend of Rational, and
everything works. This is old news. My compilers tell me it's still true.
I'm pleased about that.

If we templatize Rational, we can do the same thing, as long as we remember
to define operator* inside Rational. For example, this will compile and
link on the three compilers I'm using here (VC7.1, gcc 3.4.2, and Comeau
4.3.3). (It won't actually do anything, but all I care about right now is
compiling and linking.)

template<typename T>
class Rational1 {
public:
Rational1(T num = 1, T denom = 0) {}

friend const Rational1 operator*(const Rational1&, const Rational1&)
{ return 1; }
};

int main()
{
Rational1<int> r1;
r1 * r1; // same-type multiplication
r1 * 2; // mixed-type multiplication
2 * r1; // mixed-type multiplication
}

But suppose I don't want to define operator* inline. My understanding was
that the following would work, and I could swear that at one point I tested
this (which I stole almost verbatim from Dan Saks' "Making New Friends"
talk, and I *know* that Dan tests everything he talks about):

template<typename T>
class Rational2;

template<typename T>
const Rational2<T> operator*(const Rational2<T>&, const Rational2<T>&);

template<typename T>
class Rational2 {
public:
Rational2(T num = 1, T denom = 0) {}

friend const Rational2 operator*<T>(const Rational2&, const Rational2&);
};

template<typename T>
const Rational2<T> operator*(const Rational2<T>&, const Rational2<T>&)
{ return 1; }


int main()
{
Rational2<int> r2;
r2 * r2; // fine, compiles and links
r2 * 2; // won't compile
2 * r2; // won't compile
}

So what am I doing wrong? How can I templatize both Rational and
operator*, define operator* non-inline, yet still get implicit type
conversions on both arguments?

Thanks,

Scott


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote


 


2 26th October 09:03
jarl
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes



This is not an answer, but a "question decoration":

When changing the declaration of operator* inside Rational2 to:

template<typename T>
const Rational2<T> operator*(const Rational2<T>&,const Rational2<T>&);

i.e. I removed the <T> from operator*<T>, then it compiles but it
doesn't link...(VC 7.1)..???

-=jarl


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
3 26th October 09:04
jean-marc bourguet
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


Scott Meyers <Usenet@aristeia.com> writes:


My understanding is that when template argument deduction is done,
no conversion other that CV conversion and derived to base are
considered.

In the first case, the friend operator* was not a template
specialization (if you wanted to define it out of line, you'd have to
define one for every specialization of Rational1).

The only way I know of getting implicit type conversion with template
specialization is to use explicit template argument:

operator*<int>(r2, 2)

in your case. Not really user friendly. You can consider defining 3
operator* and not depending on the implicit type conversion.
Alternatively using an inline friend operator which forward to an out
of line template function which does the work.

Yours,

--
Jean-Marc

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
4 30th October 12:52
gabriel dos reis
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


Scott Meyers <Usenet@aristeia.com> writes:

| I hate it when I find out that I don't understand things I thought I
| understood.
|
| Suppose we have a class Rational and we want to support mixed-type
| multiplication between Rationals and ints. To get the necesary implicit
| type conversions, we declare operator* a non-member friend of Rational, and
| everything works. This is old news. My compilers tell me it's still true.
| I'm pleased about that.
|
| If we templatize Rational, we can do the same thing, as long as we remember
| to define operator* inside Rational. For example, this will compile and
| link on the three compilers I'm using here (VC7.1, gcc 3.4.2, and Comeau
| 4.3.3). (It won't actually do anything, but all I care about right now is
| compiling and linking.)
|
| template<typename T>
| class Rational1 {
| public:
| Rational1(T num = 1, T denom = 0) {}
|
| friend const Rational1 operator*(const Rational1&, const Rational1&)

This delcares a non-template friend function. In particular, implicit
conversion are honored when calling this function with set of
arguments that do not directly match the template parameters.

[...]

| template<typename T>
| class Rational2;
|
| template<typename T>
| const Rational2<T> operator*(const Rational2<T>&, const Rational2<T>&);
|
| template<typename T>
| class Rational2 {
| public:
| Rational2(T num = 1, T denom = 0) {}
|
| friend const Rational2 operator*<T>(const Rational2&, const Rational2&);

This is using the *template* function defined at global scope. In
particular, each time it is called (trough the "usual" syntax),
template argument deduction process gets into the picture. As a
consequence, almost all implicit conversions are suppressed.
Therefore, arguments must match in type. In particular, the implicit
conversion defined in that class will not be used when matching
arguments type to parameter types.

--
Gabriel Dos Reis
gdr@integrable-solutions.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
5 30th October 12:52
hyman rosen
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


Here, operator* is not a template. It's name is injected into global scope
for purposes of ADL, and then normal overload resolution finds it when doing the mixed-type arithmetic.


Here, operator* is a template. Now, the mixed-type arithmetic has to do
template argument deduction, not overload resolution. The rules for matching
against class or class reference function template parameters don't allow for
type conversions.

So go with the first approach. If you don't want all the implementation inline,
just forward to an out-of-line function that does the real work. It can even be
a template!

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
6 30th October 12:53
chris uzdavinis
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


Rational2<T>&) ....

Since templates want exact matches without conversions, I think your
requirements conflict. One solution is to add more overloads as a way
to bypass the requrement for implicit conversions:
template <typename T1, typename T2>
const Rational2<T1> operator*(const Rational2<T1> & lhs, T2 rhs) {
return lhs * Rational2<T1>(rhs); }
template <typename T1, typename T2>
const Rational2<T2> operator*(T1 lhs, const Rational2<T2> & rhs) {
return Rational2<T2>(lhs) * rhs;
}


Now anything that can be used to construct a Rational2 can be
multiplied against it, even values that need conversions (like longs
and shorts converting to int.)

--
Chris


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
7 30th October 12:53
peter dimov
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


Rational2<T>&)


Nothing wrong. Argument deduction can't deduce the T in Rational2<T>
from 2, and the overload is dropped from the set of candidates.
Implicit conversions never get a chance; the friendship is irrelevant.

You need to provide the mixed operator* overloads.

The reason the original inline friend works is that it defines a
_non-template_ operator* for every instantiation of Rational2<X>. Something like
const Rational2<int>
operator*(const Rational2<int>&, const Rational2<int>&)
{ return 1; }

(except that it is invisible to normal lookup.)


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
8 30th October 12:53
fuz
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


The 'out-of-line' operator* being defined is at global scope, as
opposed to the pseudo-global-member thing that the Barton-Nackman trick
takes advantage of. And the global version doesn't work because
conversions don't apply when deducing templated arguments.

I don't think there's any way to define it outside, other than to
forward the call to a non-inline function and hope that the compiler is
clever enough to optimise away the extra return value:
template<typename T> class Rational2;
template <typename T>
const Rational2<T> multiply(const Rational2<T>&, const
Rational2<T>&);
template<typename T>
class Rational2 {
public:
Rational2(T num = 1, T denom = 0) {}


friend const Rational2 operator*(const Rational2& lhs, const
Rational2& rhs)
{ return multiply(lhs, rhs); } };
template <typename T>
const Rational2<T> multiply(const Rational2<T>&, const Rational2<T>&)
{ return 1; } int main() {
Rational2<int> r2;
r2 * r2; // fine, compiles and links
r2 * 2; // also this
2 * r2; // me too
}

(compiled and linked on VC7.1 and GCC3.2, compiled only on the Comeau
online compiler)


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
9 30th October 12:53
bo persson
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


"Scott Meyers" <Usenet@aristeia.com> skrev i meddelandet
news:MPG.1c58aaae19e870919897af@news.hevanet.com.. .


Guess you can't! :-)

The templated operator*() "obviously" needs two parameters of identical
type, like in case 1. Otherwise it won't be instantiated, and cannot be
part of the overload resolution.

The first version is not a template, so it is available as soon as the
Rational1<int> is instatiated. Thus it can take part in the overload
resolution for the expressions.

If you look at the 5(!) versions of operator+() for std::string, you'll
see what has to be done. Hard work!


Bo Persson

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
10 30th October 12:54
gabriel dos reis
External User
 
Posts: 1
Default Mixed-type arithmetic with templatized classes


Hyman Rosen <hyrosen@mail.com> writes:

| > friend const Rational1 operator*(const Rational1&, const Rational1&)
| > { return 1; }
| > };
|
| Here, operator* is not a template. It's name is injected into global scope
| for purposes of ADL,

No, it is not. Ordinary name lookup will never find :perator*.
Remember that ADL is nothing but ordinary unqualified name lookup at
some designated scopes.

--
Gabriel Dos Reis
gdr@integrable-solutions.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
  Reply With Quote
Reply


Thread Tools
Display Modes




666