![]() |
|
|
|
|
1
26th October 09:01
External User
Posts: 1
|
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! ] |
|
|
|
|
2
26th October 09:03
External User
Posts: 1
|
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! ] |
|
|
3
26th October 09:04
External User
Posts: 1
|
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! ] |
|
|
4
30th October 12:52
External User
Posts: 1
|
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! ] |
|
|
5
30th October 12:52
External User
Posts: 1
|
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! ] |
|
|
6
30th October 12:53
External User
Posts: 1
|
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! ] |
|
|
7
30th October 12:53
External User
Posts: 1
|
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! ] |
|
|
8
30th October 12:53
External User
Posts: 1
|
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! ] |
|
|
9
30th October 12:53
External User
Posts: 1
|
"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! ] |
|
|
10
30th October 12:54
External User
Posts: 1
|
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! ] |
|