Re: operator overloading
Greg Herlihy wrote:
chris wrote:
Having recently switched to gcc 4.1.0, one of my
libraries has stopped compiling. I tracked this down
to a change in the rules used to resolve the best
match for overloaded operators. Before re-writing
everything, I'm trying to find out if this change is
fully standard compliant. Here is an example of the
problem:
| class y { };
| template <typename T>
| void bar( const T& x ) { std::cout << "nada\n"; }
| template <typename T>
| void humbug( const T& x ) { bar(x); }
| void bar( int ) { std::cout << "int\n"; }
| void bar( y ) { std::cout << "y\n"; }
Because of the (strange) difference in overloading behaviour
between fundamental types and classes, humbug(5) only
searches before the template declaration for matches and so
prints "nada", but humbug( y() ) will also search after
the declaration and so prints "y". The former is new
behaviour in 4.1.0 -- earlier version printed "int" -- but
seems to be prescribed by a defect report to the standard.
The question here is at what point in the translation unit
(source file) is humbug()'s call to bar() instantiated.
Because the point of bar()'s instantiation will determine
which overloads are visible to the compiler and thus will be
able to participate in the overload resolution process.
Yes, but that's not really the problem here.
The C++ Standard has to say about a function template's point of
instantiation:
"...if the specialization is implicitly instantiated because it is
referenced from within another template specialization and the context
from which it is referenced depends on a template parameter, the point
of instantiation of the specialization is the point of instantiation of
the enclosing specialization. Otherwise, the point of instantiation for
such a specialization immediately follows the namespace scope
declaration or definition that refers to the
specialization."[?14.6.4.1/1]
Note that this really only refers to the instantiation of the
definition. The instantiation of the declaration will
participate in overload resolution, which pretty much implies
that it is visible at the point of use (and thus the point of
instantiation is before the point of use).
The call to bar() is a type of function call described in the
first sentence. So wherever humbug<int>() is instantiated,
bar<int>() will be instantiated as well. The second sentence
describes where humbug<int> will be instantiated - and that
location is - just after its definition.
So to illustrate:
class Y { };
template <typename T>
void bar( const T& x )
{
std::cout << "nada\n";
}
template <typename T>
void humbug( const T& x )
{
bar(x);
}
<-- gcc 4.1: humbug<int> bar<int> instantiated
<-- gcc 4.0: humbug<int> instantiated
Certainly not. That's in direct contradiction to the paragraph
you quote. The point of instantiation of humbug<int>
"immediately follows the namespace scope declaration or
definition that resolution to the specialization". There's
nothing here that refers to humbug<int>.
And of course, bar<int> will be instantiated in the same context
as humbug<int>, according to the first sentence you quoted.
void bar( int ) { std::cout << "int\n"; }
void bar( Y ) { std::cout << "Y\n"; }
int main()
{
humbug(5);
}
<-- gcc 4.0 bar<int> instantiated (erroneously)
Both should be instantiated here. At least as far as the
definitions are concerned.
Since gcc 4.1 now instantiates bar<int> where the Standard
says it should be instantiated, the bar(int) function overload
that was visible from the old location is no longer visible.
The reason the bar(int) function isn't found is that name lookup
at the point of instantiation is ADL, and int isn't in any
namespace. By your logic, humbug(y()) wouldn't find bar(Y)
either, but it does. Because y IS defined in a namespace (the
global namespace), so ADL finds the function.
Therefore bar(int) in no longer a function candidate when gcc
4.1 resolves humbug()'s call to bar(). Note that a forward
declaration of bar(int) would be enough to restore the old
behavior and have the compiler select it as the best matching
bar() function overload.
However, 4.1.0 is also different in how overloading
interacts with (multiple) namespace declarations. If I
modify the above slightly:
| namespace A {
| template <typename T>
| void bar( const T& x ) { std::cout << "nada\n"; }
| template <typename T>
| void humbug( const T& x ) { bar(x); }
| }
| namespace A {
| void bar( y ) { std::cout << "y\n"; }
| }
then a call to A::humbug( y() ) now prints "nada". I has
always thought that namespaces were extensible in the sense
that
| namespace A { some code; }
| namespace A { some more code; }
is equivalent to
| namespace A { some code; some more code; }
but this is clearly not the case for resolving operator
overloading in gcc 4.1.0 .
The two "A" namespaces do combine to form a single namespace -
eventually - that is, once the compiler has "seen" them both.
After all, when the compiler is compiling source code that
comes after the first "A" namespace but before the second, it
has no way of knowing that A is about to be reopened and added
to.
And according to Standard, humbug<int> has to be instantiated
just after the close of its enclosing namespace.
No. Read what you quoted. humbug<int> has to be instantiated
immediately after the namespace scope declaration or definition
which triggered the instantiation.
In other words: just after the first namespace A but before
the second. So only those declarations in the first namespace
A scope are visible to the compiler when humbug()'s call to
the overloaded bar() function is resolved.
Does anybody know if this is the correct behaviour?
It looks correct to me.
It's correct, but not at all for the reasons you give.
--
James Kanze GABI Software
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
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]