Re: Rationale for ADL-only 2nd-phase lookup
On 8/22/07 8:41 AM, in article
1187789200.788467.181630@50g2000hsm.googlegroups.com, "Richard Smith"
<richard@ex-parrot.com> wrote:
// Declared in "serialization.h"
namespace serialization {
template <class T>
void serialize( ostream& os, T const& obj );
}
// Declared in "dump.h"
namespace serialization {
template <class T> void dump() {
T obj = ... ;
serialize( std::cout, obj );
}
}
// Declared in "my_type.h"
class my_type {};
namespace serialization {
void serialize( ostream& os, my_type const& obj );
}
If the three headers in the example were included in the order listed,
the code will break when dump<my_type>() is instantiated because the
my_type overload of serialize can only be found via ordinary lookup
from the template instantiation context (#4 in the list above), and in
a conforming compiler, that doesn't occur. But reorder the headers so
that "my_type.h" is included before "dump.h" and everything will work
fine. In general, it's not reasonable to argue that "dump.h" should
include "my_type.h", and requiring a particular order of #includes is
very fragile, especially if an incorrect order does not result in a
compile-time error.
But it is reasonable to require that a function be declared before it is
called. So the only "serialize()" functions that can match the call in
dump() are the ones declared at the point that dump() is defined in the
translation unit. After all, the programmer who wrote dump() could not have
intended to call a serialize() routine that is not declared by the point of
the call to serialize(); since there would be no assurance in that case that
this yet undeclared serialize() routine would ever be declared in the
translation unit - and therefore no assurance that the intended serialize()
would be the one called in dump(). So the author of dump() must have have
made sure that the declaration for the serialize() intended to be invoked -
had already been included in the translation unit before dump() called it.
More importantly, this rule prevents some other serialize() routine whose
declaration is included only after dump()'s definition (and a routine that
the dump()'s author may know nothing about) is mistakenly considered for a
match for the serialize() function call. Indeed, without this rule, there
would be nothing that dump()'s author could do that would prevent the wrong
serialize() function from being invoked (except perhaps by choosing an
obscure function name - like serialize23() - that would be unlikely to be
used by anyone else).
A program can easily avoid these sorts of problems by adhering to a simple
convention: place all functions declarations before all function definitions
in a translation unit. In this way, every function defined in a translation
unit has a declaration that is visible to every other function defined
within the same translation unit. So in this case, one solution would be to
create an additional header file, say, "user_specializations.h" that would
forwardly-declare all of the program's overloaded specialize() routines -
and do so in one place:
// user_specializations.h
class my_type;
namespace specialization {
void serialize( ostream& os, const my_type& obj);
...
}
So if "specialization.h" includes "user_specializations.h" then the relative
order in which "dump.h" and "my_type.h" are included by a source file - will
not matter a bit.
Greg
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]