Implicit conversion for operator

P

pikalaw

Need help figuring out why the following code fails to compile:

#include <complex>
#include <boost/rational.hpp>

int main()
{
using namespace std;
using namespace boost;

complex<rational<int> > z;
const complex<rational<int> > j(0,1);

z = 3 + 2 * j; // (1). won't
compile. complains about no matching operator*.
z = 3 + rational<int>(2) * j; // (2). but this works.
note the explicit conversion.

return 0;
}

The compiler error, generated by gcc version 4.0.1 (Apple Inc. build
5490), was

boost/operators.hpp:251: note:
candidates are: boost::rational<int> boost::eek:perator*(const
boost::rational<int>&, const int&)
boost/operators.hpp:251: note:
boost::rational<int> boost::eek:perator*(const int&, const
boost::rational<int>&)
boost/operators.hpp:251: note:
boost::rational<int> boost::eek:perator*(const
boost::rational<int>&, const boost::rational<int>&)

What bugs [pun] me is why didn't the compiler try the operator

std::complex<boost::rational<int> >
std::eek:perator*(
const boost::rational<int>&,
const std::complex<boost::rational<int> >&)

for (1) by promoting the integer constant 2 to rational<int>? But if
I explicitly convert it as in (2), then it finds this operator.

Please illuminate me!
 
P

pikalaw

An update:

Even this simplified code does not compile.

#include <complex>

using namespace std;

int main()
{
complex<long > y;
const complex<long > k(0,1);

y = 2 * k;

return 0;
}

But if I replace the constant 2 with 2L, the long version, then it
compiles.
 
I

Ian Collins

pikalaw said:
An update:

Even this simplified code does not compile.

#include <complex>

using namespace std;

int main()
{
complex<long > y;
const complex<long > k(0,1);

y = 2 * k;

return 0;
}

But if I replace the constant 2 with 2L, the long version, then it
compiles.

What else did you expect? int != long.
 
V

Victor Bazarov

pikalaw said:
Need help figuring out why the following code fails to compile:

#include <complex>
#include <boost/rational.hpp>

int main()
{
using namespace std;
using namespace boost;

complex<rational<int> > z;
const complex<rational<int> > j(0,1);

z = 3 + 2 * j; // (1). won't
compile. complains about no matching operator*.
z = 3 + rational<int>(2) * j; // (2). but this works.
note the explicit conversion.

return 0;
}

The compiler error, generated by gcc version 4.0.1 (Apple Inc. build
5490), was

boost/operators.hpp:251: note:
candidates are: boost::rational<int> boost::eek:perator*(const
boost::rational<int>&, const int&)
boost/operators.hpp:251: note:
boost::rational<int> boost::eek:perator*(const int&, const
boost::rational<int>&)
boost/operators.hpp:251: note:
boost::rational<int> boost::eek:perator*(const
boost::rational<int>&, const boost::rational<int>&)

What bugs [pun] me is why didn't the compiler try the operator

std::complex<boost::rational<int> >
std::eek:perator*(
const boost::rational<int>&,
const std::complex<boost::rational<int> >&)

for (1) by promoting the integer constant 2 to rational<int>? But if
I explicitly convert it as in (2), then it finds this operator.

Please illuminate me!

The library only defines, literally:

template<class T> complex<T> operator*(const T&, const complex<T>&);

operator for multiplication. For the expression '2 * j' it tries to
come to a conclusion what 'T' should be. It can be 'int', or it can be
'rational<int>'. The implicit conversions are not applied when deducing
template arguments. If 'T' is 'int' (from the first argument), the
second argument does not match. If it's 'rational<int>' (from the
second argument), the first argument's type does not match. So, the
compiler discards the template operator*. No other operator* suits the
expression since 'complex<rational<int> >' is not convertible to any of
the multipliers of 'int'.

If it's unclear, ask more questions.

V
 
S

Stephen Horne

An update:

Even this simplified code does not compile.
complex<long > y;
const complex<long > k(0,1);

y = 2 * k;
But if I replace the constant 2 with 2L, the long version, then it
compiles.

When you use the literal 2, I suspect two conversions are needed...

2 -> 2L (from integer to long)

2L -> <complex 2> (from long to complex)

IIRC, conversions are only done implicitly if they can be achieved in
a single step.
 
P

pikalaw

pikalaw said:
Need help figuring out why the following code fails to compile:
#include <complex>
#include <boost/rational.hpp>
int main()
{
using namespace std;
using namespace boost;
                   complex<rational<int> > z;
        const   complex<rational<int> > j(0,1);
        z = 3 + 2 * j;                         // (1).  won't
compile.  complains about no matching operator*.
        z = 3 + rational<int>(2) * j;     // (2).  but this works.
note the explicit conversion.
        return 0;
}
The compiler error, generated by gcc version 4.0.1 (Apple Inc. build
5490), was
boost/operators.hpp:251: note:
        candidates are: boost::rational<int> boost::eek:perator*(const
boost::rational<int>&, const int&)
boost/operators.hpp:251: note:
        boost::rational<int> boost::eek:perator*(const int&, const
boost::rational<int>&)
boost/operators.hpp:251: note:
        boost::rational<int> boost::eek:perator*(const
boost::rational<int>&, const boost::rational<int>&)
What bugs [pun] me is why didn't the compiler try the operator
        std::complex<boost::rational<int> >
        std::eek:perator*(
                const boost::rational<int>&,
                const std::complex<boost::rational<int> >&)
for (1) by promoting the integer constant 2 to rational<int>?  But if
I explicitly convert it as in (2), then it finds this operator.
Please illuminate me!

The library only defines, literally:

    template<class T> complex<T> operator*(const T&, const complex<T>&);

operator for multiplication.  For the expression '2 * j' it tries to
come to a conclusion what 'T' should be.  It can be 'int', or it can be
'rational<int>'.  The implicit conversions are not applied when deducing
template arguments.  If 'T' is 'int' (from the first argument), the
second argument does not match.  If it's 'rational<int>' (from the
second argument), the first argument's type does not match.  So, the
compiler discards the template operator*.  No other operator* suits the
expression since 'complex<rational<int> >' is not convertible to any of
the multipliers of 'int'.

It seems that implicit type-conversion is only allowed with non-
template functions. Had I declared

complex<long> operator*(long a, const complex<long> &z)

then the expression '2 * k' compiles fine.

However, with template version of functions, implicit type-conversion
is not done.

BTW, is this behavior in the C++ standard? Or one of those
unspecified, implementation-dependent behavior?
 
P

pikalaw

When you use the literal 2, I suspect two conversions are needed...

  2 -> 2L  (from integer to long)

  2L -> <complex 2> (from long to complex)

IIRC, conversions are only done implicitly if they can be achieved in
a single step.

I checked the stl source, the template defines the operator template

complex<T> operator*(const T &, const complex<T>);

So, I had expected this template to instantiate into

complex<long> operator*(const long &, const complex<long> &)

which with one implicit conversion from int to long would allow '2 *
k' be called.

But it didn't.
 
J

James Kanze

No. The restriction only applies to user defined conversions.
If complex wasn't a template (or if the called function wasn't a
template), then there would be no problem.
I checked the stl source, the template defines the operator template
complex<T> operator*(const T &, const complex<T>);
So, I had expected this template to instantiate into
complex<long> operator*(const long &, const complex<long> &)
which with one implicit conversion from int to long would
allow '2 * k' be called.

The problem is precisely that type deduction doesn't succeed for
the template. Type deduction using the first argument results
in T == int, and type deduction using the second argument
results in T == long. The compiler cannot choose between them.
(In type deduction, very few conversions are considered;
considering all conversions would far too often result in
ambiguity.) And since type deduction fails, there is no
instantiation (i.e. no function) for the compiler to do overload
resolution on.
 
J

James Kanze

On Jul 24, 11:14 pm, Victor Bazarov <[email protected]> wrote:

[...]
It seems that implicit type-conversion is only allowed with
non- template functions. Had I declared
complex<long> operator*(long a, const complex<long> &z)

then the expression '2 * k' compiles fine.
However, with template version of functions, implicit
type-conversion is not done.
BTW, is this behavior in the C++ standard? Or one of those
unspecified, implementation-dependent behavior?

It's the required behavior, according to the standard. You're
confusing two separate issues: operator overload resolution and
template type deduction. A function template is NOT a function,
and is not considered directly by operator overloading. If
there is a function template in scope, the compiler will try to
deduce its arguments, in order to instantiate it; if this
succeeds, the instantiated instance (which is a function) will
be added to the overload set, to be considered by overload
resolution. Very few conversions are considered (cv-qualifier
conversions, I think, and not much else) for template argument
deduction, because otherwise, one would end up with far too many
possibilities.
 
A

Alf P. Steinbach

* James Kanze:
[...]
It seems that implicit type-conversion is only allowed with
non- template functions. Had I declared
complex<long> operator*(long a, const complex<long> &z)

then the expression '2 * k' compiles fine.
However, with template version of functions, implicit
type-conversion is not done.
BTW, is this behavior in the C++ standard? Or one of those
unspecified, implementation-dependent behavior?

It's the required behavior, according to the standard. You're
confusing two separate issues: operator overload resolution and
template type deduction. A function template is NOT a function,
and is not considered directly by operator overloading. If
there is a function template in scope, the compiler will try to
deduce its arguments, in order to instantiate it; if this
succeeds, the instantiated instance (which is a function) will
be added to the overload set, to be considered by overload
resolution. Very few conversions are considered (cv-qualifier
conversions, I think, and not much else) for template argument
deduction, because otherwise, one would end up with far too many
possibilities.

One of them (it's actually worthwhile to know about in order to write templates
that work more generally than usual, but then requires some support):

<code>
template< typename T >
struct B {};

template< typename T >
void foo( B<T> const& ) {}

struct D: B<double> {};

int main()
{
foo( D() );
}
</code>


Cheers,

- Alf
 
P

Paul Brettschneider

pikalaw said:
An update:

Even this simplified code does not compile.

#include <complex>

using namespace std;

int main()
{
complex<long > y;
const complex<long > k(0,1);

y = 2 * k;

return 0;
}

But if I replace the constant 2 with 2L, the long version, then it
compiles.

Yes, that is not very comfortable. You could solve the problem in a very
hackish way:
template <typename T1,typename T2>
typename std::complex<T1> operator*(typename std::complex<T1> c, T2 v)
{
return c * T1(v);
}
template <typename T1,typename T2>
typename std::complex<T1> operator*(T2 v, typename std::complex<T1> c)
{
return c * T1(v);
}

One major problem with this approach is that when you multiply a
complex<short> with a long, you would expect to obtain a complex<long> and
not a truncated complex<short>.
 
P

pikalaw

On Jul 24, 11:14 pm, Victor Bazarov <[email protected]> wrote:

    [...]

 If
there is a function template in scope, the compiler will try to
deduce its arguments, in order to instantiate it; if this
succeeds, the instantiated instance (which is a function) will
be added to the overload set, to be considered by overload
resolution.  

I'm not so sure that an instantiated instance will be considered for
overlaoding. The following code does not compile:

template<class T>
void f(T, T) { }

template void f(long, long); // let's force an instantiation.

int main()
{
f(1L, 2L); // ok.
f(1L, 2); // no matching function

return 0;
}
[...]

--
James Kanze (GABI Software)             email:[email protected]
Conseils en informatique orientée objet/
                   Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
 
P

pikalaw

No.  The restriction only applies to user defined conversions.
If complex wasn't a template (or if the called function wasn't a
template), then there would be no problem.


The problem is precisely that type deduction doesn't succeed for
the template.  Type deduction using the first argument results
in T == int, and type deduction using the second argument
results in T == long.  The compiler cannot choose between them.
(In type deduction, very few conversions are considered;
considering all conversions would far too often result in
ambiguity.)  And since type deduction fails, there is no
instantiation (i.e. no function) for the compiler to do overload
resolution on.

Thanks. That clears it up.
 
J

James Kanze

On Jul 24, 11:14 pm, Victor Bazarov <[email protected]> wrote:
[...]
If there is a function template in scope, the compiler will
try to deduce its arguments, in order to instantiate it; if
this succeeds, the instantiated instance (which is a
function) will be added to the overload set, to be
considered by overload resolution.
I'm not so sure that an instantiated instance will be
considered for overlaoding.

Of course it is. Otherwise, it couldn't be called.
The following code does not compile:
template<class T>
void f(T, T) { }
template void f(long, long); // let's force an instantiation.

int main()
{
f(1L, 2L); // ok.
f(1L, 2); // no matching function

OK. I see what you're getting at. And you're right: we need to
find a clearer way of explaining it. The expression f(1L, 2L)
triggers the instantiation of f(long, long), and that function
is considered instantiated within that expression. This fact is
not taken into consideration in any other expression, however;
for f(long, long) to be taken into consideration in the
expression f(1L, 2), that expression must trigger the
instantiation---possible instantiations elsewhere don't count.

Technically, it would be very hard to specify if they did---what
about instantiations triggered from a different translation
unit, or even in a different function? And very hard to
implement. And would also cause no end of problems for
understanding the code---modifications in another function could
change the semantics of this function.
 

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

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top