I read the section of the standard on Koenig lookup a while back and I
don't think I really get what it is for and why it is important.
Could somebody post a simple example that demonstrates how Koenig lookup
comes in to play, and what the benefit is or what problem it solves?
Also, I would like to get some simple code I can feed to all my compilers
to see if they properly support Koenig lookup or not.
If this is already available on the web somewhere, can someone post a link
to it?
Best Regards,
-Tom Wenisch
Computer Architecture Lab
Carnegie Mellon University
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
--
Maciej Sinilo,
7th Sense s.r.l. (http://www.7th-sense.net)
I'm glad you asked!
The main problem it was designed to solve was to support operators in
namespaces. If I write a numeric type:
namespace my
{
class integer
{
...
};
integer operator+(integer, integer); // addition
std::ostream& operator<<(std::ostream& s, integer); // i/o
integer pow(integer, integer); // exponentiation
}
I want to be able to add these integers without adding a lot of namespace
qualification:
namespace yours
{
template <class T>
T compute(T x, T y, T z)
{
return x + y + z;
}
}
Without Koenig lookup (properly known as "argument-dependent lookup"), there
would be no way to find the addition operator in namespace my. To see this
more clearly, imagine compute() needed to do exponentiation. With
argument-dependent lookup, we can write:
pow(x, y)
Without Koenig lookup, it would have to write:
namespaceof(T)::pow(x, y)
which would be incredibly cumbersome. Applying that to operator+(), it looks
more like:
namespaceof(T)::operator+(x, y)
Ick! Well, we don't really have a namespaceof() operator, so we can't write
that anyway. In fact, the template example is impossible without something
like Koenig lookup.
When does Koenig lookup come into play? Any time you call a function without
explicit :: qualification. It can also come into play when you construct a
type without explicit :: qualification, e.g.
f(my_vector(x))
The compiler doesn't know whether my_vector is a type or a function from its
name, so it may use argument-dependent lookup to find whatever it can.
Argument-dependent lookup basically makes all the properly-named functions
in all of the namespaces of a function's arguments (and the enclosing
namespaces, and the namespaces of the arguments' base classes) available for
overload resolution. The rules are actually slightly more complicated than
that, but you get the idea.
If that sounds very liberal and somewhat dangerous to you, I agree! It means
that you must be very careful to qualify function calls which may operate on
arguments from other namespaces (or arguments with base classes in other
namespaces) with <namespace>:: unless you want Koenig lookup to come into
play.
> Also, I would like to get some simple code I can feed to all my compilers
> to see if they properly support Koenig lookup or not.
namespace X { struct A {}; int f(A) { return 0; } }
int z = f(X::A());
> If this is already available on the web somewhere, can someone post a link
> to it?
You might look at http://www.gotw.ca/gotw/030.htm for a different view. Herb
asserts at the end that Koenig lookup isn't a "hole" in namespaces. In his
follow-on article at http://www.gotw.ca/publications/mill02.htm he describes
the "Interface Principle", which is basically a way to understand the
behavior of and reasoning behind Koenig Lookup. While it does that, I still
think there's a gaping hole: Koenig lookup breaks the protection that ought
to be provided by namespaces.
The problem is twofold:
1. Unqualified calls are the most natural and easy way to call a function.
2. when no matching function is available in the remote namespace, an
unqualified call will match a locally-defined function.
#1 means that without exercising incredible care, most of my function calls
are actually susceptible to Koenig lookup.
#2 means that my unit tests are likely never to detect a call which should
have been qualified, since the function I actually intended from my own
namespace will be used instead. The compiler isn't allowed to issue an
error, since unqualified calls are perfectly legal.
This problem is especially bad for template code, which often ships before
anyone gets to test it with the actual function arguments that will be used.
HTH,
Dave
The motivating example is:
namespace mine
{
class C
{
// ...
};
std::ostream operator << (std::ostream&, const C&);
}
int main()
{
mine::C c;
std::cout << c; // problem here
return 0;
}
Without Koening lookup there's no visible << operator that takes the two
argument types in the call in main.
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
"Koenig lookup" is usually called "argument-dependent lookup" (ADL)
these days. The original Koenig lookup was meant to address the
following sort of situation:
void iteration(Math::Matrix *A, Math::Matrix const &Q) {
using Math::operator*;
using Math::operator-;
*A = *A - Math::transpose(Q)*(*A)*Q;
}
Some people didn't like the need to for those using-declarations.
The explicit qualification of transpose was considered OK, but
with operators we'd get something complicated involving
Math::operator- and such.
So a rule was added to the language to the effect that operators
would be looked up not only using the ordinary rules (ordinary
lookup), but also in the namespaces associated with the types of
the operands (i.e., arguments). So in the above code snippet,
operator* would be looked up in the namespace of the type of Q
and *A (i.e., Math), and in the namespace of type of the result
of Math::transpose (presumably also Math).
When the export model for templates was developed, a mechanism
was needed to bridge translation units. Eventually, "Koenig
lookup" was generalized (and for a while people talked about
both "Koenig lookup" and "extended Koenig lookup) to also apply
to non-operator calls: The function name is looked up in the
namespaces associated with the types of the arguments. In the
case of a template instantiation, this lookup may occur across
namespace definitions in different translation units (TUs).
(In all cases, we're talking about unqualified named calls;
i.e., the function or operator must be named without a '::'.
It also should not have explicit template arguments.)
> Also, I would like to get some simple code I can feed to all my compilers
> to see if they properly support Koenig lookup or not.
// Classic
namespace N {
struct S {};
S operator+(S const&, S const&) { return S(); }
}
void f1(N::S const &s) {
s+s; // If this compiles, your compiler probably
} // supports "class Koenig lookup"
// Nondependent ADL
namespace N {
void g(S const&) {}
}
void f2(NN::S const &s) {
g(s); // If this compiles, your compiler probably
} // support nondependent ADL (extended Koenig
// lookup).
// Dependent ADL
void g1(float) {}
template<typename T> void f3(T a) {
g1(a);
}
void g1(double) {}
int main() {
f3(9); // If this compiles, your compiler probably
return 0; // implements dependent name ADL correctly.
}
The last example deserves perhaps a comment. When a function
(or operator) call has an argument whose type is dependent on
a template parameter, its lookup is split in two parts. The
first lookup occurs when the template itself is parsed and is
only ordinary lookup. The second lookup occurs where the template
is instantiated, and involves _only_ ADL (no ordinary lookup).
The results of both lookups are then combined.
In this case, the call "g1(a)" is dependent and ordinary lookup
finds g1(float). At the point of instantiation, we look up
g1 again using argument-dependent lookup again, but the argument
has type "int" (the type of "9"), and "int" has no associated
namespace. So ADL really finds nothing and the only candidate
function if g1(float): That's what ought to be called.
Many compilers will do both lookups at the point of instantiation
of f3<int> (which is at the end of this program), and as a result
they will find both g1(float) and g1(double), which in turn leads
to an overload resolution ambiguity.
Daveed
> I read the section of the standard on Koenig lookup a while back and I
> don't think I really get what it is for and why it is important.
If you call a function by namespace the system looks for that function in
that namespace.
If you call a function without specifying the namespace the system looks for
that function in the namespaces of the function arguments. So your code can
be more general. You can write a template function and just call a generic
function like swap, and the compiler will find that function in the
appropriate namespace. Eg.
template <class Class>
void f(Class& c1, Class& c2) {
swap(c1,c2);
// may call std::swap, myspace::swap, etc
// whichever namespace Class resides in
}
Not sure if this is a good reason, or if there are any other reasons.
Happy to learn...
--
+++++++++++
Siemel Naran
Koenig lookup is essential to having overloaded operators and became crucial
when the Standard C++ I/O Stream library was designed. Let me give you an
example:
x.h:
#include <iostream>
namespace x {
class x_class {
private
void print() { std::cout << "Hello, World!" << std::endl; }
friend std::ostream& operator<<(std::ostream& os, const x_class&
x_obj) {
x_obj.print();
return os;
}
};
}
main.cpp:
#include "x.h"
int main() {
x::x_class x_obj;
std::cout << x_obj << std::endl; // *1*
}
Without Koenig lookup, the code in line *1* won't work because the compiler
wouldn't look for operator<< defined in namespace x. But with Koenig lookup,
the compiler also looks in the namespaces associated with the arguments
(here "std" for std::ostream and "x" for x::x_class).
This was crucial for the I/O stream library because it provides default
operator<< for the standard data types and a number of STL classes.
HTH,
sm
Thanks to all for the informative replies on argument dependant name
(a.k.a. Koenig) lookup.
Based on the explanations everyone provided, I discovered that I myself
have been depending on ADL in my own code without realizing it. Anytime I
overload an operator<< for ostream, I have been taking advantage of
ADL. Pete Becker gave this example, and it could have been taken right
out of my own code [slightly edited to conserve space]:
namespace mine {
class C { };
std::ostream operator << (std::ostream&, const C&);
}
int main() {
mine::C c;
std::cout << c; // problem here
}
It never occurred to me that the << operator should not be visible in
main(), but, clearly, without ADL, it would not be. Based on the
fact that I have been using this feature correctly without even knowing
about it, ADL strikes me generally as a Good Thing. However, I have some
concerns about templates and ADL (below).
Herb Sutter's GOTW article does a great job justifying wht Koenig lookup
does exactly what we would expect. http://www.gotw.ca/gotw/030.htm
My experience (that it did what I expected without me knowing) seems to
support this conclusion.
Dave Abrahams provided the following concise example for testing compilers
to see if they support ADL:
> namespace X { struct A {}; int f(A) { return 0; } }
> int z = f(X::A());
Using this, I confirmed that gcc 3.1 and Intel C++ 6.0 support ADL without
any need for command line switches (I gather this is not the case for all
EDG based compilers, and that Intel C++ 5.0 required an explicit command
line switch to turn dependant lookup on).
Daveed Vandevoorde gave an additional example and discussed the interplay
between ADL and two-phase name lookup for templates. Before reading this,
I was convinced that ADL was great (Herb's article sold me), and made the
world a better place. However, the GOTW article does not address the
issue of templates and the new problems this causes.
In general, when ADL causes problems, they are not that bad. If I add a
new function f() in namespace X, and ADL finds this new overload for some
code in namespace Y, the compiler gives me an error that the use in Y is
ambigous. As long as the compiler gives an error, I can fix Y to
explicitly qualify my call, and disaster is averted.
But, after playing around for a while, I was able to construct the
following example:
struct A {};
namespace X {
struct B : public A {};
#ifdef OOPS
template <class T>
void f( T & ) {
std::cout << "X::B::f<T>() called.\n";
}
#endif
}
void f( A & ) {
std::cout << " ::f() called.\n";
}
int main() {
X::B someB;
f(someB);
}
If I build this without OOPS defined, the code compiles correctly, and
prints " ::f() called.\n". B is implicitly converted to its base type A,
and f(A &) is called.
However, if a define OOPS, the code SILENTLY changes meaning. Now, the
template function in namespace X will be considered by ADL. Since the
template instantiation X::f<B> is a better match (no conversions) than
::f() (requires conversion to base), X::f<B> gets called.
If namespace X happened to be in a different source file, I can get
silently screwed.
Should I understand Herb's interface principle to mean that ANY template
defined in the same namespace as any class should be considered part of
the interface of that class? That seems to be the result that ADL leads
to.
Do people have advice on how to avoid this problem? Should I explicitly
qualify calls in functions that take a "suspicous" type as a
parameter? Should I explicitly qualify *all* calls in templates? Should
I isolate templates in a separate namespace from types to prevent ADL from
causing these silent changes?
Many thanks to all for their responses,
See the section "Name Lookup, Namepaces, and the Interface Principle" (Items
31-34) in my book Exceptional C++ (www.gotw.ca/publications/xc++.htm).
Earlier versions of some of that material are available for reading online
at:
What's In a Class? - The Interface Principle
(C++ Report, 10(3), March 1998)
www.gotw.ca/publications/mill02.htm
Herb
---
Herb Sutter (www.gotw.ca)
Secretary, ISO WG21/ANSI J16 (C++) standards committee (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
C++ community program manager, Microsoft (www.gotw.ca/microsoft)
[snip]
> When does Koenig lookup come into play? Any time you call a function without
> explicit :: qualification. It can also come into play when you construct a
> type without explicit :: qualification, e.g.
>
> f(my_vector(x))
>
> The compiler doesn't know whether my_vector is a type or a function from its
> name, so it may use argument-dependent lookup to find whatever it can.
Can Koenig lookup ever make my_vector a type? Can you give an example?
As I understand the language, if my_vector is not a visible type from
the current scope it is assumed to be a function, possibly found via
Koenig lookup. If my_vector is not a function then it's an error.
If Koenig lookup does find types why do we need typename?
template <class T> struct A {
void g (T t) { f (typename T::my_vector(t)); }
}
That is, why is 'typename T::' necessary here?
Mike
> > When does Koenig lookup come into play? Any time you call a function
without
> > explicit :: qualification. It can also come into play when you construct
a
> > type without explicit :: qualification, e.g.
> >
> > f(my_vector(x))
> >
> > The compiler doesn't know whether my_vector is a type or a function from
its
> > name, so it may use argument-dependent lookup to find whatever it can.
>
> Can Koenig lookup ever make my_vector a type? Can you give an example?
Please see Gabriel Dos Reis' reply at
http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view%20audit-trail&database=gcc&p
r=7100 for an explanation.
-Dave
I don't understand why Koenig lookup is essential for operator overloading.
Couldn't people just put their operator overloads at global namespace?
> Daveed Vandevoorde gave an additional example and discussed the interplay
> between ADL and two-phase name lookup for templates. Before reading this,
> I was convinced that ADL was great (Herb's article sold me), and made the
> world a better place. However, the GOTW article does not address the
> issue of templates and the new problems this causes.
The problems are not actually template-specific.
We could rewrite that example thus:
struct A {};
// header X.hpp
namespace X {
struct B : public A {};
#ifdef OOPS
inline void internal_helper( B& ) {
std::cout << "X::internal_helper() called.\n";
}
inline void public_function(B& b) {
internal_helper(b);
...
}
#endif
}
// main.cpp
namespace {
void internal_helper( A & ) {
std::cout << " ::internal_helper() called.\n";
}
}
void f( B& ) {
internal_helper(b);
...
}
int main() {
X::B someB;
f(someB);
}
No templates involved. The point is that there's no agreement about the
meaning of the name "internal_helper" between the authors of the two files.
> If namespace X happened to be in a different source file, I can get
> silently screwed.
I don't understand this part. Namespaces are not (neccessarily) located in
any given source file.
> Should I understand Herb's interface principle to mean that ANY template
> defined in the same namespace as any class should be considered part of
> the interface of that class? That seems to be the result that ADL leads
> to.
Now I see your point. Any function template which can match the class as an
argument, yes.
So, not:
template <class T> void f(T(*)());
for example. In fact, the category of functions in Interface Principle's
notion of "interface" is a little bigger than I said, because:
template <class T> void f(T*);
for example, is also part of the interface of any class in the same
namespace.
However, my point is that what to consider "part of the interface" is really
irrelevant to the problem. The real problem has to do with "agreed-upon
points of customization".
> Do people have advice on how to avoid this problem? Should I explicitly
> qualify calls in functions that take a "suspicous" type as a
> parameter? Should I explicitly qualify *all* calls in templates? Should
> I isolate templates in a separate namespace from types to prevent ADL from
> causing these silent changes?
Here's what you have to do, IMO, templates or no templates.
1. Come to grips with the fact that every unqualified call with arguments
that may come from a namespace other than the caller's is potentially a
"customization point" for the caller. In other words, the function you're
calling without qualification might turn out to be something other than the
one you think you're calling when you write the code. Someone can customize
the behavior of your code by providing a better match for your call in the
appropriate namespace. That can happen without templates because someone can
insert a new function declaration in a header file you're using. This can be
a good thing, *if and when* it's what you want.
2. Decide what your desired "points of customization" are, and document
them.
3. Do something to suppress Koenig lookup on all other calls. This is the
nasty part, because it requires us to be hyper-conscious of the most obvious
function calls: those to functions defined in the same namespace as the
caller! What's most-concise way to accomplish that?
Let me ask this question first: How can I suppress Koenig lookup at ***?
namespace foo {
struct X {};
void f(X) {}
namespace {
int f(X) { return 0; }
int g() { return f(X()); } // ***
}
}
int x = foo::g();
There's only one way, which is always the tersest way AFAIK:
int g() { return (f)(X()); }
Yep, believe it. Parens suppress Koenig lookup (thanks to Steve Adamczyk for
pointing that out to me just yesterday).
4. Read up on the points-of-customization provided by the code you're using
(e.g. libraries, or code from your colleagues). Make sure you avoid using
those signatures unless you intend to define a customization.
Just in case it wasn't obvious, my problem with Koenig lookup here is that
it's biased towards making everything into a point-of-customization, whereas
in real code there are usually only a few places where people intend to
provide one.
-Dave
> [ stuff to be printed and pinned at the wall ]
> The last example deserves perhaps a comment. When a function
> (or operator) call has an argument whose type is dependent on
> a template parameter, its lookup is split in two parts. The
> first lookup occurs when the template itself is parsed and is
> only ordinary lookup. The second lookup occurs where the template
> is instantiated, and involves _only_ ADL (no ordinary lookup).
> The results of both lookups are then combined.
>
> In this case, the call "g1(a)" is dependent and ordinary lookup
> finds g1(float). At the point of instantiation, we look up
> g1 again using argument-dependent lookup again, but the argument
> has type "int" (the type of "9"), and "int" has no associated
> namespace. So ADL really finds nothing and the only candidate
> function if g1(float): That's what ought to be called.
What was the reason for introducing this little extra surprise for
the unawared?
I often think of templates as rules/instructions for building a
true type/class and I regard instantiation as the place where these
instructions are used. I never thought things included at this point
could stay "invisible", like g1(double) in Your example.
Normally I innclude orthogonal "rules" from different headers and
combine those at the point of instantiation without worrying about
the order in which I included my headers.
I just started wondering when I will get bitten by the rule
above ... but maybe one gets into more trouble _without_ the rule.
Could You show that?
regards,
Markus
> Do people have advice on how to avoid this problem? Should I explicitly
> qualify calls in functions that take a "suspicous" type as a
> parameter? Should I explicitly qualify *all* calls in templates? Should
> I isolate templates in a separate namespace from types to prevent ADL from
> causing these silent changes?
My suggestion: use descriptive names for functions. Use operator
overloading only when the operator invocation clearly has the desired
meaning under all circumstances. Use templates only when every
possible instantiation does the right thing.
It is common in C++ that users don't know what version of an
overloaded function is invoked. All overloaded functions together form
a single interface; it is the task of the implementor of such
functions to make sure that all implementations of an overloaded
function follow the documented behaviour. This, as a user of this
interface, you should never need to worry which specific function is
called. In cases where the functions differ in documented behaviour,
the user has the choice to pick a particular function if needed.
Regards,
Martin
>Using this, I confirmed that ... Intel C++ 6.0 support ADL without
>any need for command line switches.
To me, it still requires to instruct the front-end with a
-Qoption, c, --arg_dep_lookup
or
-Qoption, cpp, --arg_dep_lookup
Just in case it matters, what build do you have?
Genny.
I'm not a fan of C++ namespaces, nor of ADL. So yes, I'd suggest
inhibiting ADL where possible/practical. Some observations:
. ADL is already inhibited if ordinary lookup finds a
member. So if in "f(a)" f is found to be a member function
after ordinary lookup, no additional lookup (ADL) is done.
Still, my preference is to use explicit "this->" prefixes
(as in "this->f(a)") to clarify the call in the source
(and in templates to avoid surprises with dependent bases).
. You can also inhibit ADL by parenthesizing the function name
(e.g., "(f)(a)"). This could be useful, e.g., to inhibit
ADL on a call to a function declared in an unnamed namespace.
. I don't know of a good way to inhibit ADL on operators
(excluding new/delete ;-).
As you note, extra care is especially important in templates.
Daveed
On 16 Jul 2002, Gennaro Prota wrote:
> On 15 Jul 2002 15:41:04 -0400, Thomas Wenisch <twen...@ece.cmu.edu>
> wrote:
>
> >Using this, I confirmed that ... Intel C++ 6.0 support ADL without
> >any need for command line switches.
>
> To me, it still requires to instruct the front-end with a
>
> -Qoption, c, --arg_dep_lookup
> or
> -Qoption, cpp, --arg_dep_lookup
>
>
> Just in case it matters, what build do you have?
>
I am using the Linux version of icc. I have heard that the Linux and
Windows versions behave different in this regard, so, if you are using the
Windows one, that is probably the reason for the difference.
Here's the output of icc -V:
Intel(R) C++ Compiler for 32-bit applications, Version 6.0 Build 020312Z
Copyright (C) 1985-2002 Intel Corporation. All rights reserved.
I was able to compile Dave's example code with just icc <filename> without
error.
> Genny.
>
Best Regards,
-Tom Wenisch
Computer Architecture Lab
Carnegie Mellon University
On 16 Jul 2002, David Abrahams wrote:
> > Daveed Vandevoorde gave an additional example and discussed the interplay
> > between ADL and two-phase name lookup for templates. Before reading this,
> > I was convinced that ADL was great (Herb's article sold me), and made the
> > world a better place. However, the GOTW article does not address the
> > issue of templates and the new problems this causes.
>
> The problems are not actually template-specific.
[ my example snipped ]
> We could rewrite that example thus:
>
> struct A {};
>
> // header X.hpp
> namespace X {
> struct B : public A {};
>
> #ifdef OOPS
> inline void internal_helper( B& ) {
> std::cout << "X::internal_helper() called.\n";
> }
>
> inline void public_function(B& b) {
> internal_helper(b);
> ...
> }
> #endif
> }
>
> // main.cpp
> namespace {
> void internal_helper( A & ) {
> std::cout << " ::internal_helper() called.\n";
> }
> }
>
> void f( B& ) {
> internal_helper(b);
> ...
> }
>
> int main() {
> X::B someB;
> f(someB);
> }
>
> No templates involved. The point is that there's no agreement about the
> meaning of the name "internal_helper" between the authors of the two files.
>
I see. So the real key was that I was matching the function in my
"local" namespace via an implicit conversion, whereas the function in the
namespace examined via Koenig lookup was an exact match, and therefore
"wins" overload resolution. Templates or no templates. If I already have
a perfect match against a function in the local namespace, the worst
Koenig lookup can do is cause an ambiguity compile error.
> > Should I understand Herb's interface principle to mean that ANY template
> > defined in the same namespace as any class should be considered part of
> > the interface of that class? That seems to be the result that ADL leads
> > to.
>
> Now I see your point. Any function template which can match the class as an
> argument, yes.
> So, not:
>
> template <class T> void f(T(*)());
>
> for example. In fact, the category of functions in Interface Principle's
> notion of "interface" is a little bigger than I said, because:
>
> template <class T> void f(T*);
>
> for example, is also part of the interface of any class in the same
> namespace.
>
This makes sense. From this, I take away that I should use quite a bit of
care in deciding exactly where templates whose arguments can be deduced
automatically should live.
> However, my point is that what to consider "part of the interface" is really
> irrelevant to the problem. The real problem has to do with "agreed-upon
> points of customization".
>
> > Do people have advice on how to avoid this problem? Should I explicitly
> > qualify calls in functions that take a "suspicous" type as a
> > parameter? Should I explicitly qualify *all* calls in templates? Should
> > I isolate templates in a separate namespace from types to prevent ADL from
> > causing these silent changes?
>
> Here's what you have to do, IMO, templates or no templates.
>
> 1. Come to grips with the fact that every unqualified call with arguments
> that may come from a namespace other than the caller's is potentially a
> "customization point" for the caller. In other words, the function you're
> calling without qualification might turn out to be something other than the
> one you think you're calling when you write the code. Someone can customize
> the behavior of your code by providing a better match for your call in the
> appropriate namespace. That can happen without templates because someone can
> insert a new function declaration in a header file you're using. This can be
> a good thing, *if and when* it's what you want.
>
> 2. Decide what your desired "points of customization" are, and document
> them.
>
This strikes me as analogous to considering that every virtual function
could be overridden by a subclass introduced by someone else. Now I must
apply the same consideration to all unqualified calls.
> 3. Do something to suppress Koenig lookup on all other calls. This is the
> nasty part, because it requires us to be hyper-conscious of the most obvious
> function calls: those to functions defined in the same namespace as the
> caller! What's most-concise way to accomplish that?
>
> Let me ask this question first: How can I suppress Koenig lookup at ***?
>
> namespace foo {
> struct X {};
> void f(X) {}
> namespace {
> int f(X) { return 0; }
> int g() { return f(X()); } // ***
> }
> }
> int x = foo::g();
>
> There's only one way, which is always the tersest way AFAIK:
>
> int g() { return (f)(X()); }
>
> Yep, believe it. Parens suppress Koenig lookup (thanks to Steve Adamczyk for
> pointing that out to me just yesterday).
>
Neat trick!
> 4. Read up on the points-of-customization provided by the code you're using
> (e.g. libraries, or code from your colleagues). Make sure you avoid using
> those signatures unless you intend to define a customization.
>
From reading the thread discussing specializing std::swap<>, it seems that
customizing even where customization might be benificial/welcome can
sometimes be problematic. I have not encountered any libraries which take
advantage of ADL for this kind of customization (at least, not that I have
noticed in the documenation). Are there any public examples of this kind
of design out there? Do any parts of Boost or the Standard library take
advantage of this kind of customization?
> Just in case it wasn't obvious, my problem with Koenig lookup here is that
> it's biased towards making everything into a point-of-customization, whereas
> in real code there are usually only a few places where people intend to
> provide one.
>
> -Dave
>
Thanks for your comments.
Best Regards,
-Tom Wenisch
Computer Architecture Lab
Carnegie Mellon University
> From reading the thread discussing specializing std::swap<>, it seems
that
> customizing even where customization might be benificial/welcome can
> sometimes be problematic. I have not encountered any libraries which take
> advantage of ADL for this kind of customization (at least, not that I have
> noticed in the documenation). Are there any public examples of this kind
> of design out there? Do any parts of Boost or the Standard library take
> advantage of this kind of customization?
Sure, the standard library has quite a few of those: std::accumulate comes
to mind: it uses operator+ on the sequence elements, for example.
Boost's lexical_cast<> uses ADL to find the streaming operator.
There's a get_pointer() function which is used by the Boost mem_fun library.
I think that uses ADL.
I think the Boost Graph Library is generally using ADL with get() and put()
functions on property maps.
Even Boost authors have only recently become aware of the breadth-of-reach
of ADL, so there are probably hundreds of calls in our codebase which still
could use ADL unintentionally. I've been lobbying for compiler vendors to
give us a "Possible ADL" warning, with some kind of #pragma to let us say "I
really mean to use ADL on this name in this scope" so that we can start
uncovering the true extent of the problem. No luck so far, though...
-Dave
>Thomas Wenisch <twen...@ece.cmu.edu> wrote:
>[...]
>> Do people have advice on how to avoid this problem? Should I explicitly
>> qualify calls in functions that take a "suspicous" type as a
>> parameter? Should I explicitly qualify *all* calls in templates? Should
>> I isolate templates in a separate namespace from types to prevent ADL from
>> causing these silent changes?
>
>I'm not a fan of C++ namespaces, nor of ADL. So yes, I'd suggest
>inhibiting ADL where possible/practical. Some observations:
> . ADL is already inhibited if ordinary lookup finds a
> member.
Only for "named" calls? For instance, when an operator is normally
referenced in an expression non-member candidates are always searched,
and the search is done according to ADL rules (excluding members, of
course - 13.3.1.2/3)
struct L;
namespace Right {
struct R {
};
void operator + (const L&, const R&);
}
struct L {
void operator + (const Right::R&) const {}
};
int main() {
L left;
Right::R right;
left+right; // ambiguous: Right::operator +
// is a candidate
}
Actually, I'm quite sure that this is how things are supposed to work,
but two points are unclear to me by reading the standard:
a) 3.4.2 starts by saying: "When an unqualified *name* is used as the
postfix-expression in a function call (5.2.2), other namespaces not
considered during the usual unqualified lookup (3.4.1) may be
searched".
The intent seems to be that entire paragraph applies only when the
postfix-expression is a name, i.e. (the use of) an identifier. Right?
But when the postfix expression is of the form operator@,(i.e. when
you are following the commandments of 13.3.1.2/3) it is not an
identifier. It's an operator-function-id.
Was the intent to say 'unqualified-id' instead of 'name'?
b) What happens if I use the function call syntax directly?
operator + (left, right); // what lookup here?
> So if in "f(a)" f is found to be a member function
> after ordinary lookup, no additional lookup (ADL) is done.
> Still, my preference is to use explicit "this->" prefixes
> (as in "this->f(a)") to clarify the call in the source
> (and in templates to avoid surprises with dependent bases).
> . You can also inhibit ADL by parenthesizing the function name
> (e.g., "(f)(a)").
Cool trick. Where did you learn it? :-)
> This could be useful, e.g., to inhibit
> ADL on a call to a function declared in an unnamed namespace.
> . I don't know of a good way to inhibit ADL on operators
> (excluding new/delete ;-).
I'm not sure about using the parenthesis-enclosed operator function
id... what do you think?
P.S.: Looking at ADL, the profane said: "Hmmm.... if operator
overloading is syntactic sugar, this must be diabetes!" :-)
Genny.
Adam H. Peterson
> I don't understand why Koenig lookup is essential for
> operator overloading. Couldn't people just put their
> operator overloads at global namespace?
Yup. This is a lot simpler to understand and has the
added advantage of working with compilers that don't
support Koenig lookup.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
> . You can also inhibit ADL by parenthesizing the function name
> (e.g., "(f)(a)"). This could be useful, e.g., to inhibit
> ADL on a call to a function declared in an unnamed namespace.
Is this really true? I've always considered that the two truisms -- (a)
== a and +a == a -- always held, in all programming languages.
--
James Kanze mailto:jka...@caicheuvreux.com
Conseils en informatique oriente objet/
Beratung in objektorientierter Datenverarbeitung
That kind of defeats the purpose of namespaces. If, for example, I want a
different implementation for the standard output operator for ints, I can
put that in a different namespace and call it from in there. Also, friend
declarations of global namespaces are quite a hassle.
sm
Here are a few examples:
Given a class X and a member i, you can do "&X::i" but you can't do
"&(X::i)".
Similarly, "int f(int())" will declare a function f that takes one argument
of type "function with no arguments returning int" and that returns an int.
"int f((int())" is a variable f of type int intialized with a
default-initialized int. Now, you might think that nobody writes stuff like
this, but trust me on that people do (usually not with int's though, and
usually expecting the result of the latter with the syntax of the former).
sm
> > . You can also inhibit ADL by parenthesizing the function name
> > (e.g., "(f)(a)"). This could be useful, e.g., to inhibit
> > ADL on a call to a function declared in an unnamed namespace.
>
> Is this really true?
It's debatable. 3.4.2 start with
"When an unqualified name is used as the postfix-expression in a
function call (5.2.2),"
Clearly, "f" is an unqualified name. One can argue whether "(f)" also
is an unqualified name. If you look at the definition of
postfix-expression (in 5.2/1), none of those sounds like "unqualified
name". Instead, 3.4.2 clearly talks about "primary-expression"; and
most people apparently read it as
primary-expression -> id-expression -> unqalified-id -> identifier
however
primary-expression -> ( expression ) ->* ( primary-expression ) -> ( identifier )
also matches the formulation "unqualified name", IMO.
Regards,
Martin
| ka...@gabi-soft.de (James Kanze) writes:
|
| > > . You can also inhibit ADL by parenthesizing the function name
| > > (e.g., "(f)(a)"). This could be useful, e.g., to inhibit
| > > ADL on a call to a function declared in an unnamed namespace.
| >
| > Is this really true?
|
| It's debatable. 3.4.2 start with
|
| "When an unqualified name is used as the postfix-expression in a
| function call (5.2.2),"
|
| Clearly, "f" is an unqualified name. One can argue whether "(f)" also
| is an unqualified name. If you look at the definition of
| postfix-expression (in 5.2/1), none of those sounds like "unqualified
| name". Instead, 3.4.2 clearly talks about "primary-expression"; and
| most people apparently read it as
|
| primary-expression -> id-expression -> unqalified-id -> identifier
|
| however
|
| primary-expression -> ( expression ) ->* ( primary-expression ) -> ( identifier )
|
| also matches the formulation "unqualified name", IMO.
However when you use the production
(expression)
to parse (f) -- semantic analysis has to whether that expression is
well-formed -- you can't use it to perform Koenig lookup. The issue
has been debated (at least on the Core Working Group list), it is was
clarified that it was meant a function-call syntax with no
funny parenthesis around the unqualified-id. That is,
"unqualified-id" was really meant.
--
Gabriel Dos Reis, dos...@cmla.ens-cachan.fr
Thanks for the clarification. This was exactly what I asked in one of
my other posts of this thread.
I really wonder *why* they didn't write "unqualified-id", anyway. Even
EDG seems to have been deceived by that wording: in fact if in the
example I gave
struct L;
namespace Right {
struct R {
};
void operator + (const L&, const R&);
}
struct L {
void operator + (const Right::R&) const {}
};
int main() {
L left;
Right::R right;
left+right; // ambiguous: Right::operator +
// is a candidate
}
you change 'left+right' to 'operator + (left, right)' then Comeau
online (4.3.0) doesn't give any error.
Am I missing something or is it worth a bug report to Greg? (Well,
considering the wording I think it's a little unfair to call it a
bug...)
Genny.
The first of those statements was not even true for Standard C. Placing
parentheses round a function name inhibited the preprocessor from
treating it as a function like macro.
The second statement ceased to be universally true when C++ allowed user
written operator+ functions with a single operand.
..
--
Francis Glassborow ACCU
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
> However when you use the production
>
> (expression)
>
> to parse (f) -- semantic analysis has to whether that expression is
> well-formed -- you can't use it to perform Koenig lookup. The issue
> has been debated (at least on the Core Working Group list), it is was
> clarified that it was meant a function-call syntax with no
> funny parenthesis around the unqualified-id. That is,
> "unqualified-id" was really meant.
That is no real clarification. unqualified-id is defined as
unqualified-id:
identifier
operator-function-id
conversion-function-id
~class-name
template-id
Does that mean that one has to use ADR for all these names? As Gennaro
points out, this question is particularly interesting for
operator-function-id, since one might expect that operator+(foo,bar)
is subject to ADR as well. However, I would not expect that
template-id is.
Regards,
Martin
| Gabriel Dos Reis <g...@merlin.nerim.net> writes:
|
| > However when you use the production
| >
| > (expression)
| >
| > to parse (f) -- semantic analysis has to whether that expression is
| > well-formed -- you can't use it to perform Koenig lookup. The issue
| > has been debated (at least on the Core Working Group list), it is was
| > clarified that it was meant a function-call syntax with no
| > funny parenthesis around the unqualified-id. That is,
| > "unqualified-id" was really meant.
|
| That is no real clarification. unqualified-id is defined as
|
| unqualified-id:
| identifier
| operator-function-id
| conversion-function-id
| ~class-name
| template-id
|
| Does that mean that one has to use ADR for all these names?
[ An aside question you use ADR for "Argument Dependent ?xxx?" ]
| As Gennaro
| points out, this question is particularly interesting for
| operator-function-id, since one might expect that operator+(foo,bar)
| is subject to ADR as well.
Yep :-)
I agree that it is unfortunate that the standard doesn't use
unqualified-id consistently. At least that would make thinks
clearer.
A slightly related note (issue 218),
http://anubis.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#218
should probably address the issue definitely.
| However, I would not expect that template-id is.
Yep. I think there is even an explicit bullet in the standard saying
that Koenig lookup doesn't apply for qualified-id.
--
Gabriel Dos Reis, dos...@cmla.ens-cachan.fr
<snip>
> > 4. Read up on the points-of-customization provided by the code you're
using
> > (e.g. libraries, or code from your colleagues). Make sure you avoid
using
> > those signatures unless you intend to define a customization.
> >
>
> From reading the thread discussing specializing std::swap<>, it seems
that
> customizing even where customization might be benificial/welcome can
> sometimes be problematic. I have not encountered any libraries which take
> advantage of ADL for this kind of customization (at least, not that I have
> noticed in the documenation). Are there any public examples of this kind
> of design out there? Do any parts of Boost or the Standard library take
> advantage of this kind of customization?
Incidentally, I have recently begun to use a technique which works quite
well for specifying whose semantics for a given function name are intended
when using an unqualified call:
namespace dave
{
enum semantics { tag }; // used only to regulate ADL
}
Now if my library defines a point-of-customization, say "swap", it is used
like this:
swap(arg1, arg2, tag);
If someone wants to define a swap with semantics that are the same as those
specified by my library:
namespace thomas
{
template <class T> class X {};
template <class T>
void swap(X<T>&, X<T>&, dave::semantics);
};
That distinguishes my idea of what swap means from any other swap. When you
implement my swap for your class, you say explicitly which swap it is that
you're implementing. Of course, swap really ought to belong to std::, but I
hope you get my point.
Interestingly, as a transition plan we could write:
template <class T>
void swap(X<T>&, X<T>&, dave::semantics = dave::tag);
which allows code using swap(x,y) directly to find our function.
-Dave
> > I don't understand why Koenig lookup is essential for
> > operator overloading. Couldn't people just put their
> > operator overloads at global namespace?
>
> Yup. This is a lot simpler to understand and has the
> added advantage of working with compilers that don't
> support Koenig lookup.
That won't work, atleast if people also put operators on namespace
level. In that case, the namespace-level operator would hide the
global operators, if you invoke the operator inside the namespace.
Regards,
Martin
>loe...@informatik.hu-berlin.de (Martin v. Lwis) writes:
>
>| Gabriel Dos Reis <g...@merlin.nerim.net> writes:
>|
>| > However when you use the production
>| >
>| > (expression)
>| >
>| > to parse (f) -- semantic analysis has to whether that expression
>| is > well-formed -- you can't use it to perform Koenig lookup. The
>| issue > has been debated (at least on the Core Working Group list),
>| it is was > clarified that it was meant a function-call syntax with
>| no > funny parenthesis around the unqualified-id. That is, >
>| "unqualified-id" was really meant.
>|
>| That is no real clarification. unqualified-id is defined as
>|
>| unqualified-id:
>| identifier
>| operator-function-id
>| conversion-function-id
>| ~class-name
>| template-id
>|
>| Does that mean that one has to use ADR for all these names?
>
>[ An aside question you use ADR for "Argument Dependent ?xxx?" ]
>
Rook up! (Martin, we are kidding, of course. Don't take it hard ;-))
>| As Gennaro
>| points out, this question is particularly interesting for
>| operator-function-id, since one might expect that operator+(foo,bar)
>| is subject to ADR as well.
>
>Yep :-)
>
>I agree that it is unfortunate that the standard doesn't use
>unqualified-id consistently. At least that would make thinks clearer.
>
>A slightly related note (issue 218),
>
> http://anubis.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#218
>
>should probably address the issue definitely.
>
Very _slightly_ related, indeed. How can it solve the issue at hand?
>| However, I would not expect that template-id is.
>
>Yep. I think there is even an explicit bullet in the standard saying
>that Koenig lookup doesn't apply for qualified-id.
^^^^
Doesn't apply for "template-ids"? Thus, the following is ill-formed:
namespace N {
class X {};
template <class T>
void f (T& t) {
}
}
int main() {
N::X val;
f<N::X> (val);
}
but it is ok if I remove '<N::X>' from the function call???
Genny.
You could still do that. And in such a situation, Koenig lookup doesn't
enter into it at all. So I still don't see where Koenig lookup is the
savior of operator overloading.
> Also, friend
> declarations of global namespaces are quite a hassle.
I hardly think this is a convincing rationale. Friend declarations in
any other namespace are a hassle, so by such reasoning, we oughtn't use
namespaces at all.
Friend declarations in the global namespace require 2 (count 'em: 2)
more characters. And I hardly ever implement my operator overloads as
"friend" functions. (They seldom need access to protected and private
members.)
| goo...@vandevoorde.com (Daveed Vandevoorde) wrote in message
| news:<52f2f9cd.02071...@posting.google.com>...
|
| > . You can also inhibit ADL by parenthesizing the function name
| > (e.g., "(f)(a)"). This could be useful, e.g., to inhibit
| > ADL on a call to a function declared in an unnamed namespace.
|
| Is this really true?
Yes: (f)(a) does not have the function-call syntax.
| I've always considered that the two truisms -- (a)
| == a and +a == a -- always held, in all programming languages.
However, not all programming languages have the same syntax -- another
truism.
You're talking about semantics equivalence. Koenig-lookup is about
syntax.
--
Gabriel Dos Reis, dos...@cmla.ens-cachan.fr
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Garry Lancaster:
> > Yup. This is a lot simpler to understand and has the
> > added advantage of working with compilers that don't
> > support Koenig lookup.
Sebastian Moleski:
> That kind of defeats the purpose of namespaces.
This is what some people say about argument-dependent
lookup. I think you have unwittingly provided an example.
> If, for example, I want a different implementation for the
> standard output operator for ints, I can put that in a
> different namespace and call it from in there.
Let me be sure I understand you: you want to do something
like this:
#include <iostream>
namespace ns
{
std::ostream& operator<<(std::ostream& os, int i)
{ os << "ns::operator<<\n"; return os; }
void call_op()
{ std::cout << 1; }
}
int main()
{ ns::call_op(); }
and you think that it works using argument-dependent
lookup?
Try it. It doesn't work *at all*: the compiler will complain
of ambiguity between your operator<< and one in std.
Without argument-dependent lookup it would work.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
Garry Lancaster:
> > Yup. This is a lot simpler to understand and has the
> > added advantage of working with compilers that don't
> > support Koenig lookup.
Martin v. Lwis:
> That won't work, atleast if people also put operators on namespace
> level. In that case, the namespace-level operator would hide the
> global operators, if you invoke the operator inside the namespace.
That's right. Herb Sutter's "Exceptional C++" gives an example
of this using std::accumulate and operator+.
But I think we have to ask why did people put operators inside
namespaces in the first place: it only works because of Koenig
lookup. If we are saying we need Koenig lookup because of
Koenig lookup, it all becomes rather circular.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
That's right (by 14.2/2). I remember when Mike Ball pointed out
that this would otherwise be nearly impossible to parse (this was
at a special Core III WG meeting convened not too long after ADL
was generalized in support of export). It didn't make me like ADL
any more.
Daveed
[...]
| >I agree that it is unfortunate that the standard doesn't use
| >unqualified-id consistently. At least that would make thinks clearer.
| >
| >A slightly related note (issue 218),
| >
| > http://anubis.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#218
| >
| >should probably address the issue definitely.
| >
|
| Very _slightly_ related, indeed. How can it solve the issue at hand?
My point it that that issue is intended to clarify the specification
of Koenig lookup. I find it quite reasonable to take the opportunity
to clarify the terminology. Don't you?
| >| However, I would not expect that template-id is.
| >
| >Yep. I think there is even an explicit bullet in the standard saying
| >that Koenig lookup doesn't apply for qualified-id.
| ^^^^
|
| Doesn't apply for "template-ids"?
Yes -- that is an unfortunate typo Martin pointed out to me in a private
mail. Sorry for the confusion.
| Thus, the following is ill-formed:
|
| namespace N {
|
| class X {};
|
| template <class T>
| void f (T& t) {
| }
|
| }
|
| int main() {
|
| N::X val;
| f<N::X> (val);
|
| }
|
| but it is ok if I remove '<N::X>' from the function call???
Yes, that is what the standard says. If you remove the explicit
template-argument-list specification, then the whole thing has the suitable
form digestible by Koenig-lookup (as intended).
However, with the template-argument-list, the compiler has first to
determine whether f<> really names a template. But that in turn means
that "f" should be found by ordinary name lookup -- which fails.
The chapter&verse is the note 14.8.1/6:
[Note: For simple function names, argument dependent lookup (3.4.2)
applies even when the function name is not visible within the scope
of the call. This is because the call still has the syntactic form
of a function call (3.4.1). But when a function template with
explicit template arguments is used, the call does not have the
correct syntactic form unless there is a function template with that
name visible at the point of the call. If no such name is visible,
the call is not syntactically well-formed and argument-dependent
lookup does not apply. If some such name is visible, argument
dependent lookup applies and additional function templates may be
found in other namespaces.
You might want to consulte the example that follows.
It definitely matters what the standard means by "function-call".
--
Gabriel Dos Reis, g...@integrable-solutions.net
>intended when using an unqualified call:
>
> namespace dave
> {
> enum semantics { tag }; // used only to regulate ADL
> }
>
>Now if my library defines a point-of-customization, say "swap", it is
>used like this:
>
> swap(arg1, arg2, tag);
>
>If someone wants to define a swap with semantics that are the same as
>those specified by my library:
>
> namespace thomas
> {
> template <class T> class X {};
> template <class T>
> void swap(X<T>&, X<T>&, dave::semantics);
> };
>
>That distinguishes my idea of what swap means from any other swap. When
>you implement my swap for your class, you say explicitly which swap it
>is that you're implementing. Of course, swap really ought to belong to
>std::, but I hope you get my point.
I've been reading this thread with interest. I think I have something
to contribute, but I think it may come out somewhat muddled, so I hope
you'll bear with me.
Earlier in the thread you gave an example with a function named
"internal_helper()", and showed how someone working in one namespace
might add a function with this name and have it unexpectedly affect the
behavior of working classes in another namespace. It seemed to me that
you want to "blame" ADL for causing this trouble. But now with the
"solution" you're suggesting above, I think you're really getting at the
real core of the issue, and I wanted to discuss it further.
Let us temporarily remove namespaces, ADL, and Sutter's "Interface
Principle" from the discussion. Now we can see what I think is the core
of the issue: accidental conformance via ad-hoc overloading.
(By "ad-hoc" overloading, I mean overloading a particular function name
but implementing a function with different semantics. E.g., someone
writes a function swap(TheirType&,TheirType&) for TheirType which does
not have the expected semantics--they (foolishly?) chose the name swap
to do some other arbitrary thing to a couple of TheirType objects.)
Ad-hoc overloading leads to the problem of accidental conformance. We
call a function with a particular name on an object of a particular
type, and if it successfully compiles, we assume that the behavior has
some particular semantics. E.g., in class Foo<T>, we write swap(x,y)
(where x and y are objects of type T) and expect something specific to
happen, but then are surprised when someone instantiaties Foo<TheirType>
and the behavior changes.
What we really want is named conformance: we want to check not only that
that swap(x,y) will typecheck, but also that the paricular swap we are
invoking is one whose author intended it to be an implementation of the
"swap concept" for objects of type T.
We are thwarted from easily achieving this goal due to a number of
conspiring language features. Ad-hoc overloading is a language feature
which prevents us from relying solely on the (unqualified) name of the
function to ensure we are calling an entity which implements the desired
semantics.
Going back to "full C++" (with namespaces, ADL, etc.), the availibility
of function template specializations appears to provide a solution:
rather than overload, we can intentionally specialize an existing
template. Then we can call std::swap(x,y), and only those who have
specialized std::swap will conform. I think the problem in this
particular example is that C++ doesn't have _partial_ specialization for
function templates, making it impossible for some classes to specialize
swap for themselves. As a result, people are advocating even more
ad-hoc "solutions" like having your code say
using std::swap;
swap(x,y);
to try to get the right function called. All of this is just getting
even further away from the true mark, needlessly complicating things.
What we really want is an unambiguous name to which we can tie specific
semantics. When we refer to that name, we will be assured not only that
some functionality has the right signature/shape, but also the right
semantics. This gives us named conformance (and thus avoids the
problems of accidental conformance). Your example above with dave::tag
truly "solves" the problem, because you have created a one-to-one
correspondence between a name and a piece of semantics.
(As a quick aside, I want to point out some related work. The paper on
"Static Interfaces in C++" at http://www.oonumerics.org/tmpw00/
discusses many of the same issues in the domain of "concept checking"
for templates. The programming language Haskell does not allow ad-hoc
overloading of functions, but does allow "semantic overloading" using a
language feature called "type classes".)
Once we've gotten to the core of the issue, we can back up again and ask
if/how to "fix" C++ so that we avoid the problems that you attribute to
ADL. I think that namespaces and ADL and Sutter's interface principle
are all fine--they are not the issue. I think that the real issues are
threefold:
- First, we have no agreed conventions about how to go about creating a
one-to-one correspondence between names and semantics. I think your
example above with dave::tag is an example of "a solution that
actually works". However it's by no means the 'standard' way to do
it, and it's not clear to me even that it's the best way to do it
given the existing constraints C++ imposes on us.
- Second, even if we did adopt a convention for "semantic overloading",
there would still be the problem that the Standard Library doesn't
conform to these conventions.
- Finally, the fact that we need to adopt a "convention" suggests a
weakness of the core C++ language. The language itself should
provide a particular construct which is "good at this". There
should be an _easy_ way to get a "static point of indirection"
(basically, a name) on which you can hang semantics.
Offhand, it seems to me that the language feature that would fit the
bill would be partial function template specialization. Then,
specializing an existing function template for a particular type would
communicate intent and thus ensure named/semantic conformance. The
unsuspecting user might accidentally overload the function name for his
arguments, but then his function would not get called by accident by
unsuspecting clients, because those clients would be using fully
qualified names. That is, the "real" way to "swap two Ts, using the
semantics associated with the name std::swap" would be
std::swap(x,y);
Everyone who wants to implement a swap function with those semantics is
required to specialize std::swap. std::swap becomes the name to which
the semantics are associated. Writing
swap(x,y);
is tantamount to saying "I don't care what the semantics of swap are in
this call; I just want to call whatever overloads might be found."
Ok, to sum up my opinion:
ADL is not the problem.
The real problem is ad-hoc overloading. ADL does make it easier to
create an "accidental overload" because you can "overload" a name from
another namespace, which you may be completely unaware of. However this
doesn't change the fact that the problem is still due to the
overloading.
The solution is to not rely on overload-resolution to resolve calls when
you desire particular semantics. Since overloading in C++ is ad-hoc,
relying on overload resolution to resolve calls cannot guarantee that
you get the right semantics.
A good solution may be template specialization. Unlike overloading, you
cannot "accidentally" specialize template (the syntax of a
specialization requires you to have foreknowledge of the existing
template, whereas the syntax of overloading a function name does not
require any knowledge of other overloads of the name). Thus,
specializing a template communicates the intent to semantically conform.
The fact that the existing template has a unique qualified name (e.g.
"std::swap", and not just "swap") enables arbitrary decide at each call
whether or not they intend to "call by semantics" (e.g.
std::swap) or to "call by ad-hoc overloading" (e.g. swap).
To go to a really high level now:
The real key is to realize that, in reality, C++ namespaces serve only
to shield the names of _types_ and _variables_ from similarly named
entities in other namespaces. Unqualified _function_ names, however,
cannot be shielded by namespaces. It may have been the original
intention of namespaces to provide such shielding, but ADL removes this
shielding for bare function names. While one can argue that this means
that "ADL breaks the shield; ADL is to blame", I think Sutter's
Interface Principle is a hard argument to deny--the Interface Principle
seems perfectly reasonable to me. As a result, we need to re-work the
concept of "name protection" in our minds to match what C++ actually
gives us. Namespaces provide only part of the solution to "name
protection". For the remainder, we must rely on another mechanism.
Above, I suggest specialization of (fully-qualified) template functions
as one possible solution to the "name protection" problem.
That's my take, anyway. Hope I manged to communicate it well-enough to
be understandable.
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
> Gennaro Prota <gennar...@yahoo.com> wrote:
> [...]
>> Doesn't apply for "template-ids"? Thus, the following is ill-formed:
>>
>> namespace N {
>>
>> class X {};
>>
>> template <class T>
>> void f (T& t) {
>> }
>>
>> }
>>
>> int main() {
>>
>> N::X val;
>> f<N::X> (val);
>>
>> }
>>
>> but it is ok if I remove '<N::X>' from the function call???
>
> That's right (by 14.2/2).
This is an extra argument for all those who think that C++ sucks.
> I remember when Mike Ball pointed out
> that this would otherwise be nearly impossible to parse
Nearly impossible or totally impossible?
If only nearly impossible:
Are we again at the point where compiler vendors fear
hard work and rather pollute the C++ language rules with
yet another extra pitfall instead of evolving a really usable
language?
If there is a minimum chance to avoid/remove that mess above,
then it should be taken.
> (this was
> at a special Core III WG meeting convened not too long after ADL
> was generalized in support of export). It didn't make me like ADL
> any more.
So what am I to do? Drop namespaces again?
Markus
Yes.
> But now with the
> "solution" you're suggesting above, I think you're really getting at the
> real core of the issue, and I wanted to discuss it further.
>
> Let us temporarily remove namespaces, ADL, and Sutter's "Interface
> Principle" from the discussion. Now we can see what I think is the core
>
> of the issue: accidental conformance via ad-hoc overloading.
Which ADL makes all too easy.
> (By "ad-hoc" overloading, I mean overloading a particular function name
> but implementing a function with different semantics. E.g., someone
> writes a function swap(TheirType&,TheirType&) for TheirType which does
> not have the expected semantics--they (foolishly?) chose the name swap
> to do some other arbitrary thing to a couple of TheirType objects.)
Because we're temporarily removing namespaces from the discussion I'll let
you get away with this description. In principle, namespaces should be the
mechanism that protects us from this ad-hoc overloading problem. Using the
same name as someone else did in a different namespace should generally be
safe, and not prone to "spooky action at a distance". Call me old-fashioned,
but I'm just not ready for Quantum Mechanical interactions in my programs.
> Going back to "full C++" (with namespaces, ADL, etc.), the availibility
> of function template specializations appears to provide a solution:
> rather than overload, we can intentionally specialize an existing
> template. Then we can call std::swap(x,y), and only those who have
> specialized std::swap will conform.
There are real problems with that approach also. See
http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2001/n1296.htm
(and http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2001/n1295.htm, if
you're interested in details of how it could be implemented)
...but the biggest problem with that approach is not outlined in the paper:
qualified calls are bound in templates at its point of /definition/. That
means that if I have a template algorithm:
namespace std {
template <class It>
void sort(It start, It finish) {
...
std::swap(*p, *q);
}
}
and somewhere later in the translation unit I write:
namespace std {
void swap(dave::some_class&, dave::some_class&);
}
The compiler can never use my swap.
> I think the problem in this
> particular example is that C++ doesn't have _partial_ specialization for
> function templates, making it impossible for some classes to specialize
> swap for themselves.
Unfortunately not: there's no magic bullet feature for this problem.
> Once we've gotten to the core of the issue, we can back up again and ask
> if/how to "fix" C++ so that we avoid the problems that you attribute to
> ADL. I think that namespaces and ADL and Sutter's interface principle
> are all fine--they are not the issue. I think that the real issues are
> threefold:
>
> - First, we have no agreed conventions about how to go about creating a
> one-to-one correspondence between names and semantics. I think your
> example above with dave::tag is an example of "a solution that
> actually works". However it's by no means the 'standard' way to do
> it, and it's not clear to me even that it's the best way to do it
> given the existing constraints C++ imposes on us.
I've not seen a better solution under the current language definition, and
I've been working on this problem for several years. My solution is ugly as
sin, though, I'll admit.
> - Second, even if we did adopt a convention for "semantic overloading",
> there would still be the problem that the Standard Library doesn't
> conform to these conventions.
Yep.
> - Finally, the fact that we need to adopt a "convention" suggests a
> weakness of the core C++ language. The language itself should
> provide a particular construct which is "good at this". There
> should be an _easy_ way to get a "static point of indirection"
> (basically, a name) on which you can hang semantics.
Well, yeah. But scopes should also work to protect us from having to be
vigilant about qualification everywhere.
> Ok, to sum up my opinion:
>
> ADL is not the problem.
I disagree; it does cause a serious problem.
> The real problem is ad-hoc overloading. ADL does make it easier to
> create an "accidental overload" because you can "overload" a name from
> another namespace, which you may be completely unaware of. However this
> doesn't change the fact that the problem is still due to the
> overloading.
You're missing half of the problem by only looking at the end where the
overload is defined. The really nasty stuff happens where the function is
invoked: you *must* remember to qualify your call if you don't want ADL
(whether you have FTPS - Function Template Partial Specialization - or not).
If you forget to qualify your call, your code will almost always compile and
your tests will almost certainly pass because the function you intended to
call anyway will be found. The serious problem with ADL is that it's the
default. If I write code naturally, referring to functions in the same or
enclosing namespaces without qualification, I am probably writing buggy
code. Furthermore, no compiler is allowed to stop me, because it's perfectly
legal. That's the practical problem with ADL.
<snip>
> To go to a really high level now:
>
> The real key is to realize that, in reality, C++ namespaces serve only
> to shield the names of _types_ and _variables_ from similarly named
> entities in other namespaces. Unqualified _function_ names, however,
> cannot be shielded by namespaces. It may have been the original
> intention of namespaces to provide such shielding, but ADL removes this
> shielding for bare function names.
Bingo.
> While one can argue that this means
> that "ADL breaks the shield; ADL is to blame", I think Sutter's
> Interface Principle is a hard argument to deny--the Interface Principle
> seems perfectly reasonable to me.
1. The interface principle is not an argument. It's a set of distinctions
which can help simplify thinking about namespaces and where to define your
functions.
2. The two are not in conflict. I use the interface principle, but it
doesn't change the fact that ADL leaves me vulnerable to nasty problems from
which I should be protected.
3. My only argument with Herb on this is that he claims that the interface
principle shows that namespaces aren't broken by ADL. It does not.
> As a result, we need to re-work the
> concept of "name protection" in our minds to match what C++ actually
> gives us.
We have to do that for now, but I hope we don't have to settle for that
forever. This is one area where we should make C++ more predictable. The
best C++ programmers I've ever seen are constantly writing code that invokes
ADL unintentionally. Working on the "how do you customize" problem (e.g. by
proposing FTPS) is small potatoes. We need a solution for the "how do you
prevent customization" problem.
-Dave
> But I think we have to ask why did people put operators inside
> namespaces in the first place: it only works because of Koenig lookup.
No. People put operators into namespaces to avoid those operators being
considered in contexts where they are not wanted. If you put enough
operators in global namespace, I assure you that a lot of programs would
break since operator invocations become ambiguous (because the classes
of their arguments have conversions to other classes, or primitive
types).
Regards,
Martin
[...]
| > I remember when Mike Ball pointed out
| > that this would otherwise be nearly impossible to parse
|
| Nearly impossible or totally impossible?
|
| If only nearly impossible:
| Are we again at the point where compiler vendors fear
| hard work and rather pollute the C++ language rules with
| yet another extra pitfall instead of evolving a really usable
| language?
If it is theoretically possible and pratically impossible, then I would
say "nearly impossible" and I wouldn't accuse compiler writers to "fear
hard work and rather pollute C++ language".
And, before going on accusing people in such case, personnally, I would
ask for and read the minutes of that meeting. Persumably there were a
debate (i.e. argumentations) followed by a vote. The exact arguments
(pro and cons) aren't reported here. I think it is bit too harsh to
accuse people solely based on what David reported (I'm not sayingg he is
wrong).
--
Gabriel Dos Reis, g...@integrable-solutions.net
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[...]
> What we really want is an unambiguous name to which we can tie
> specific semantics. When we refer to that name, we will be assured
> not only that some functionality has the right signature/shape, but
> also the right semantics. This gives us named conformance (and thus
> avoids the problems of accidental conformance). Your example above
> with dave::tag truly "solves" the problem, because you have created a
> one-to-one correspondence between a name and a piece of semantics.
Dave's technique doesn't solve the "accidental conformance" problem in
general. It is still possible to provide a swap() that will match, if
the third parameter is templated.
Apart from that, I agree with most of your post. Namespaces and ADL
don't create any new problems. They might not _solve_ existing problems
as well as some of us would have liked, but they don't make matters
worse.
> ...but the biggest problem with that approach is not outlined in the
> paper: qualified calls are bound in templates at its point of
> /definition/. That means that if I have a template algorithm:
>
> namespace std {
> template <class It>
> void sort(It start, It finish) {
> ...
> std::swap(*p, *q);
> }
> }
>
> and somewhere later in the translation unit I write:
>
> namespace std {
> void swap(dave::some_class&, dave::some_class&);
> }
>
> The compiler can never use my swap.
I am still looking for the reason why standard C++
was decided to behave this way. Could You explain?
Do You agree with this rule?
Markus
> > To go to a really high level now:
> >
> > The real key is to realize that, in reality, C++ namespaces serve
> only > to shield the names of _types_ and _variables_ from similarly
> named > entities in other namespaces. Unqualified _function_ names,
> however, > cannot be shielded by namespaces. It may have been the
> original > intention of namespaces to provide such shielding, but ADL
> removes this > shielding for bare function names.
>
> Bingo.
Uh, whoops. I should have said this instead:
If you believe that namespaces even shield the names of types from
similarly named entities in other namespaces, you should read Gaby's
reply to my "bug" report at
http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view%20audit-trail&database=g
cc&p
r=7100
They don't even do that :(
You can draw your own conclusions about the value of the current design
of namespaces and ADL in the standard.
-Dave
P.S. See also
http://groups.google.com/groups?dq=&hl=en&lr=&ie=UTF-8&oe=UTF-8&th=5f0f4
160e
9ac32e8&seekm=ahi8h3%24aeg%241%40nets3.rz.RWTH-Aachen.DE&frame=off for
an example of how ADL has bitten a standard library implementation.
| But I think we have to ask why did people put operators inside
| namespaces in the first place:
Surely you don't want to have the content of std::rel_ops in the global
namespace. Do you?
--
Gabriel Dos Reis, g...@integrable-solutions.net
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> > avoids the problems of accidental conformance). Your example above
> > with dave::tag truly "solves" the problem, because you have created
> > a one-to-one correspondence between a name and a piece of semantics.
>
> Dave's technique doesn't solve the "accidental conformance" problem in
> general. It is still possible to provide a swap() that will match, if
> the third parameter is templated.
Good point. You're saying that someone can provide a function template
with a fully-general argument where my algorithm is passing a tag. Oh,
well, at least that reduces the problem to an ambiguity error at worst
(if I understand the rules correctly).
> Apart from that, I agree with most of your post. Namespaces and ADL
> don't create any new problems. They might not _solve_ existing
> problems as well as some of us would have liked, but they don't make
> matters worse.
Whether or not the problems are "new" depends on where you start your
timeline. The problems are no newer than ADL itself.
ADL makes some matters better (convenient customization of behavior) and
some worse (accidental customization of behavior, or if you're lucky, an
ambiguity)
I think the convenience is not worth what we pay for it.
-Dave
I have heard two rationales:
1. It's needed to support export. I don't believe this one, BTW.
2. Allowing overloads to be found after the point of invocation would be
confusing. I don't buy this one because controlling which definitions
make it into a translation unit is much easier than controlling the
order in which they appear.
> Do You agree with this rule?
Nope.
-Dave
>Gennaro Prota <gennar...@yahoo.com> writes:
>
>[...]
>
>| >I agree that it is unfortunate that the standard doesn't use
>| >unqualified-id consistently. At least that would make thinks
clearer.
>| >
>| >A slightly related note (issue 218),
>| >
>| > http://anubis.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#218
>| >
>| >should probably address the issue definitely.
>| >
>|
>| Very _slightly_ related, indeed. How can it solve the issue at hand?
>
>My point it that that issue is intended to clarify the specification of
>Koenig lookup. I find it quite reasonable to take the opportunity to
>clarify the terminology. Don't you?
>
I should be crazy to say that! :-) What I said is that 218 is focused on
another problem, so it's unlikely that its resolution will automatically
clarify the terminology problem raised here, unless we file a separate
defect report (which then the committee may decide to resolve in
conjunction with 218)
BTW, contrary to what you said in another post, we have seen that
'unqualified-id' cannot be what is meant in 3.4.2/1. On the other hand
we cannot simply mean 'identifier'. So I think it's time to shed some
light.
Aaaaarghhhhhh!!!! Did someone see that example???? For those who are not
aware of DR241 here is the code:
namespace A {
struct B { };
template<int X> void f(B);
}
namespace C {
template<class T> void f(T t);
}
void g(A::B b) {
f<3>(b); // ill-formed: not a function call
A::f<3>(b); // well-formed
C::f<3>(b); // ill-formed; argument dependent lookup
// applies only to unqualified names
using C::f;
f<3>(b); // well-formed because C::f is visible; then
// A::f is found by argument dependent lookup
}
In short, f<3> would be a valid template-id but the compiler isn't able
to produce it, unless it happens (in the example, by mean of the using
declaration) to know that f names a template function. Of course the
function that gets called is A::f.
What actually puzzles me is the fact 1) that a parsing problem is
resolved this way (you must know the "meaning" of the identifier f) and
2) that ADL can depend on it.
One could think that
void g(A::B b) {
// no using here...
(f<3>) (b);
}
would force the 'right' production rule but this seems not to be true...
And if we didn't use those '<' and '>' for templates? Would we still
have these problems?
>It definitely matters what the standard means by "function-call".
Well, I'll make my ignorance public (if there is a need to do that
:-)): if I write
int main() {
int b;
char a;
(a)(b); // (*)
}
(*) is a function call regardless of the meaning of a, i.e. even if at
the point of the call a is not a function name, right? The grammar has
no notion of function-names, only identifiers.
Also, a template-id is an expression (like an identifier), so I can
write something like:
template <int N> void f();
void (*p)() = f<3>;
just like I would write for instance
void a();
void (*p)() = a;
Now, why (f<3>)(a) shouldn't be a (syntactically) valid function call
regardless of the meaning of f?
I'm not saying that the name lookup should search something like 'f<3>',
of course :-) Just that it could search 'f' *after* that
(f<3>)(arglist) has been parsed as a function call.
Any insight would be greatly appreciated.
Genny.
"Gabriel Dos Reis" <g...@soliton.integrable-solutions.net> wrote in
message news:m3lm7z9...@soliton.integrable-solutions.net...
> "Garry Lancaster" <glanc...@ntlworld.com> writes:
>
> | But I think we have to ask why did people put operators inside
> | namespaces in the first place:
>
> Surely you don't want to have the content of std::rel_ops in the
> global namespace. Do you?
std::rel_ops should stay safely (and uselessly) buried right where it
currently is, or eliminated from the standard library altogether.
Anyway, I don't see what that has to do anything, since ADL usually
can't find those operators...
-Dave
--
---------------------------------
David Abrahams * Boost Consulting da...@boost-consulting.com *
http://www.boost-consulting.com
Martin v. Lwis:
> No. People put operators into namespaces to avoid those
> operators being considered in contexts where they are not
> wanted. If you put enough operators in global namespace,
> I assure you that a lot of programs would break since
> operator invocations become ambiguous (because the classes
> of their arguments have conversions to other classes, or primitive
> types).
As far as I can see, putting operator overloads you wish to
be globally accessible at global scope usually leads to the
same ambiguities as putting them in the namespace of
one of their parameters and letting argument-dependent
lookup find them. In some cases, in fact, ADL creates
more ambiguities (see my example given in reply to
Sebastian Moleski).
Maybe I misunderstand your point. It would be easier to
discuss if you provided an example of what you mean.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
I think it is theoretically possible, but at a cost in compile
time and diagnostic quality that may not be acceptable to most
C++ programmers.
> If only nearly impossible:
> Are we again at the point where compiler vendors fear
> hard work and rather pollute the C++ language rules with
> yet another extra pitfall instead of evolving a really usable
> language?
Do you have any sort of evidence that compiler vendors fear
hard work? I've worked for a few of them, and hard work is
all I have seen.
"Usable" has many aspects when it comes to the design of
programming languages. The parsing requirements will affect
parsing speed and diagnostic quality. Sometimes some of that
can be sacrificed for the sake of consistency. In the above
situation, that sacrifice was not deemed worthwhile (and it
wasn't a close call either).
> If there is a minimum chance to avoid/remove that mess above,
> then it should be taken.
Not at any cost, IMO.
> > (this was
> > at a special Core III WG meeting convened not too long after ADL
> > was generalized in support of export). It didn't make me like ADL
> > any more.
>
> So what am I to do? Drop namespaces again?
I don't think namespaces are a useful enough feature to warrant
their trouble, but I think that's a minority opinion in this
community. As someone implied elsewhere in this thread, it might
be useful to just place types and variable (and templates thereof)
in namespaces and leave nonmember functions (and templates thereof)
in the file scope. It could help avoid collisions for common type
names (like "Object", "Vector", "Map", "Array", ...). I believe
that was in fact the original goal of templates.
Daveed
>One could think that
>void g(A::B b) {
> // no using here...
> (f<3>) (b);
>}
>would force the 'right' production rule but this seems not to be true...
>And if we didn't use those '<' and '>' for templates? Would we still
>have these problems?
Probably not. Rather than using parenthesis to try to overcome the
ambiguity, as you suggest, it might be the case that just allowing new
syntax like
template f<3>(b);
would give us a way to avoid the parsing nightmare. The idea is that
f() were a member function of an object x (dependent), we can call
x.template f<3>(b);
in order to let the parser know that we intend for f to be a
template-id. Perhaps we can do the same for unqualified function
names, when we want to find a template-id on another namespace using
ADL.
It isn't clear to me exactly what changes to the grammar would be
necessary to overcome the limitations here, but it is clear to me that
"parsing problems" do make it impossible to call certain functions that
you might want to call.
>Now, why (f<3>)(a) shouldn't be a (syntactically) valid function call
>regardless of the meaning of f?
>
>I'm not saying that the name lookup should search something like 'f<3>',
>of course :-) Just that it could search 'f' *after* that
>(f<3>)(arglist) has been parsed as a function call.
My impression is that asking compilers to parse that might be asking too
much, as I think it requires a potentially unbounded amount of backup
capability. But I'm a little out of my element here, so I'm not sure.
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
"David Abrahams" <david.a...@rcn.com> once said:
>Because we're temporarily removing namespaces from the discussion I'll let
>you get away with this description. In principle, namespaces should be the
>mechanism that protects us from this ad-hoc overloading problem. Using the
>same name as someone else did in a different namespace should generally be
>safe, and not prone to "spooky action at a distance". Call me old-fashioned,
>but I'm just not ready for Quantum Mechanical interactions in my programs.
Agreed.
> > Going back to "full C++" (with namespaces, ADL, etc.), the availibility
> > of function template specializations appears to provide a solution:
> > rather than overload, we can intentionally specialize an existing
> > template. Then we can call std::swap(x,y), and only those who have
> > specialized std::swap will conform.
>
>There are real problems with that approach also. See
>http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2001/n1296.htm
...
>...but the biggest problem with that approach is not outlined in the paper:
>qualified calls are bound in templates at its point of /definition/. That
>means that if I have a template algorithm:
>
> namespace std {
> template <class It>
> void sort(It start, It finish) {
> ...
> std::swap(*p, *q);
> }
> }
>
>and somewhere later in the translation unit I write:
>
> namespace std {
> void swap(dave::some_class&, dave::some_class&);
> }
>
>The compiler can never use my swap.
You've declared an overload. If instead you either (a) made swap a
template class and you partially specialized it, or (b) added partial
specialization of function templates to C++ (and you specialize), then
things work, right? Right.
I am all for "bound at the point of definition" and keeping templates
hygenic; this is one of the things the committee got absolutely right.
> > - First, we have no agreed conventions about how to go about creating a
> > one-to-one correspondence between names and semantics. I think your
> > example above with dave::tag is an example of "a solution that
> > actually works". However it's by no means the 'standard' way to do
> > it, and it's not clear to me even that it's the best way to do it
> > given the existing constraints C++ imposes on us.
>
>I've not seen a better solution under the current language definition, and
>I've been working on this problem for several years. My solution is ugly as
>sin, though, I'll admit.
A few ideas pop into my head, but I'll defer to your experience on this
point; I can easily imagine that any off-the-cuff ideas I have would
fail for one obscure reason or another.
>You're missing half of the problem by only looking at the end where the
>overload is defined. The really nasty stuff happens where the function is
>invoked: you *must* remember to qualify your call if you don't want ADL
>(whether you have FTPS - Function Template Partial Specialization - or not).
>If you forget to qualify your call, your code will almost always compile and
>your tests will almost certainly pass because the function you intended to
>call anyway will be found. The serious problem with ADL is that it's the
>default. If I write code naturally, referring to functions in the same or
>enclosing namespaces without qualification, I am probably writing buggy
>code. Furthermore, no compiler is allowed to stop me, because it's perfectly
>legal. That's the practical problem with ADL.
I disagree. I think your argument in this paragraph is extremely close
to being convincing, but I still believe that it is just a little
off-the-mark (though I will make a rather large concession in a
moment).
Here's the crux of my argument against yours: even with your solution,
you *must* remember to supply the third dave::tag parameter when the
function is invoked. Do you consider this to be an equally horrible
flaw with your solution?
If no, imagine for a moment that we've gone back in time and actually
introduced the dave::tag parameter as an actual third argument in the
standard library, and for years, everyone who wanted to make their own
swap function has been following this convention, and writing 3-argument
swap() functions.
Now, if you write code that calls
swap(x,y) // oops, how foolish of me, I called the wrong func
instead of
swap(x,y,dave::tag);
are you going to argue that it's _ADL's_fault_ that the wrong function
might get called (whether it silently works or not)? No. The key is
this: in order to ensure the right function is called, you must use
the unique name which has the one-to-one correspondence with the correct
semantics. In your solution, this means adding the third dave::tag
parameter. In my solution this means adding the std:: qualification to
the front of the call. In either case, leaving out the name-to-which-
the-semantics-are-tied is the very same kind of error. Regardless of
what the surface syntax looks like, if you call the wrong function, you
have called the wrong function, plain and simple.
Now for my concession. You're right that the "psychology of a C++
programmer" makes it such that errors of the kind where you write
swap(x,y)
instead of the intended
std::swap(x,y)
are way too easy to make. ADL compounds the problem, turning such a
"slip" into an actual "mistake" when we fail to discover the problem
because our code appears to work anyway, despite its subtle flaw. Put
another way, I agree with your overall sentiment here; the language
design here interacts very badly with the pitfalls of human
psychology.
The reason I am arguing this point at all is because I didn't agree with
how you stated things here:
>If I write code naturally, referring to functions in the same or
>enclosing namespaces without qualification, I am probably writing buggy
>code.
I dislike "naturally", because I think it covers up where the problem
lies. I think that a programmer who really understands C++ would be
careful about qualifying function names, and thus it's unreasonable to
assert that "using unqualified names is the _natural_ thing to do"
(which is how I read your statement).
When we focus on what I think is the key point--namely that in this
case:
you wrote "swap(x,y)" but you meant to write "std::swap(x,y)"
ADL promotes the "slip" into a "mistake" that we're unlikely to notice
with "working" code--then I think it also helps guide us to finding a
good remedy for the problem. In this specific example, I don't think
it's very difficult to find this kind of error in library code, provided
you design a specific test for it. (The easy thing to do is write a
class which models all of the template concepts necessary to put the
class in a vector<T> and then sort() it, with the one flaw that it has a
swap() which can be found only by ADL and whose body prints an error
message and then exits. Try to instantiate the templates with this new
type. If it fails to compile, you're good. If it compiles, runs, and
bombs out with your swap error messages, you've made an error. This is
just a small modification of the technique of using "archetype classes"
that Jeremy Siek et al use in their "concept checking" methodology.)
It is annoying to have to write this extra test case, yes. But it's
really not hard; especially if the archetype classes which would expose
these types of errors were provided by a library (e.g. Boost). My
impression is that you feel that ADL is overshadowing everything in a
way that requires a fatalistic outlook on the future of C++, and I don't
agree with that. (Or maybe I'm just reading too much "melodrama" into
this discussion thread.)
> > While one can argue that this means
> > that "ADL breaks the shield; ADL is to blame", I think Sutter's
> > Interface Principle is a hard argument to deny--the Interface Principle
> > seems perfectly reasonable to me.
>
>1. The interface principle is not an argument. It's a set of distinctions
>which can help simplify thinking about namespaces and where to define your
>functions.
>
>2. The two are not in conflict. I use the interface principle, but it
>doesn't change the fact that ADL leaves me vulnerable to nasty problems from
>which I should be protected.
>
>3. My only argument with Herb on this is that he claims that the interface
>principle shows that namespaces aren't broken by ADL. It does not.
I think agree overall here, but I want to make another point which I
think might otherwise get swept under the carpet. It requires an
illustrative example:
Suppose we have a template function (in the global namespace, say)
like this:
template <class T>
void apply_g( const T& x ) {
x.g();
}
This template works on any "T" type which is "g-able". For example, a
type like
struct Foo { void g() const; }; // Foo is "g-able"
will work. Since template concepts aren't explicitly built-in to C++,
this constraint is merely implicit in the function body.
Now imagine a similar situation:
template <class T>
void apply_g( const T& x ) {
g(x);
}
Perhaps the designer of the "g-able" classes realized that making g() a
member was overkill; a non-member was sufficent, and he used the
interface principle to justify the new design. That is, Foo has been
redesigned like so:
struct Foo { }; void g( const Foo& ); // Foo is "g-able"
Now the problem is that, _without_ ADL, the new template function
apply_g() will no longer work if Foo is declared inside a namespace.
In order to make it work, we have to do one of two things:
(1) Rewrite it like this:
template <class T>
void apply_g( const T& x ) {
namespaceof(T)::g(x);
}
using the imaginary namespaceof() operator, or
(2) Redesign things so that we reify the "g-able" concept into the code:
namespace g_able {
template <class T>
void g( const T& ); // declaration, no definition
}
...
// Recall that Foo is maybe declared in some other namespce
struct Foo { }; template <> void ::g_able::g( const Foo& );
...
template <class T>
void apply_g( const T& x ) {
g_able::g(x);
}
I am actually fond of solution (2), because it makes the template
concepts explicit and ensures me named conformance. However, I know
from experience that most C++ people seem to think that it is
impractical/wrong/something to add dozens of "concepts" like "g-able"
and "swappable" and "copy constructable" and whatever to C++ code. They
prefer to just let them be implicit, and let ad-hoc overloading and ADL
and other suspect language features allow them to write template code
based on "after the fact" concepts which happen to lie implicit in
certain types. _I_ am not fond of this attitude, but I know that many
C++ people feel this way, and I want to point out that, as a result,
those people depend on ADL in order to be able to realize such ad-hoc
designs. Since we do not have namespaceof(), I just want to make it
clear that many C++ practitioners cannot survive without ADL, because
without it, it is impossible to realize these ad-hoc designs. Indeed,
both of our solutions to the swap() issue (yours with dave::tag and
mine using partial function template specialization) are, in fact,
reifying the concept of "swappable" into the language (library, really)
by providing a unique name with a one-to-one correspondence with the
actual semantics. This is, IMO, undoubtably the _right_ thing to do.
But I have argued on this newsgroup before that "reifying template
concepts" is a good idea, and been met with a swarm of opposition by
those who fear "death by a thousand cuts" from a zillion little
concepts like "swappable".
The point of all of this is that I want you to realize that any
arguments against ADL are also implicitly arguments for reifying
template concepts (which hopefully my example above illustrates).
I think this is the right thing to do, but many people don't. You might
be able to convince them that ADL is bad, but make sure they're also
aware that without ADL, template concepts have to be made explicit in
order to realize many existing designs. That (IMO) is the harder sell.
> > As a result, we need to re-work the
> > concept of "name protection" in our minds to match what C++ actually
> > gives us.
>
>We have to do that for now, but I hope we don't have to settle for that
>forever. This is one area where we should make C++ more predictable. The
>best C++ programmers I've ever seen are constantly writing code that invokes
>ADL unintentionally. Working on the "how do you customize" problem (e.g. by
>proposing FTPS) is small potatoes. We need a solution for the "how do you
>prevent customization" problem.
I agree that this is in the best interest of the long-term health of
the language. Change is hard, though.
As an aside, someone else mentioned on this thread how code like
f<n::S>(x)
is hard/impossible to parse if you want ADL to apply to "f" but f is not
currently a template-id (but would be in the right namespace). This
tiny argument is, IMO, a "killer" one (one which I wasn't previously
aware of). If you really want to argue ADL is broken, I say use that
example, and no one can possible argue with you. :)
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
| "Gabriel Dos Reis" <g...@soliton.integrable-solutions.net> wrote in
| message news:m3lm7z9...@soliton.integrable-solutions.net...
| > "Garry Lancaster" <glanc...@ntlworld.com> writes:
| >
| > | But I think we have to ask why did people put operators inside
| > | namespaces in the first place:
| >
| > Surely you don't want to have the content of std::rel_ops in the
| > global namespace. Do you?
|
| std::rel_ops should stay safely (and uselessly) buried right where it
| currently is, or eliminated from the standard library altogether.
Certainly, some people have uses of std::rel_ops, so I don't think
they can be easily eliminated as it is to send a message to a
newgroup.
| Anyway, I don't see what that has to do anything, since ADL usually
| can't find those operators...
Please, re-read the text I quoted. I was responding to the question
as to why people put operators in namespaces.
--
Gabriel Dos Reis, g...@integrable-solutions.net
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
In that discussion in my book, I think I addressed that: For the first part,
people should put operators in the same namespace as the types they operate
on, so "why did people put operators inside namespaces" would be "because
that's where the types are."
For the second part, I argued that we need Koenig lookup because, given C++
namespaces plus the Interface Principle (which says that free functions that
mention a type and are packaged with it in the same namespace are just as
much part of the type's interface as the type's member functions), we need
to find those free functions. Koenig lookup lets us find those free
functions.
Herb
---
Herb Sutter (www.gotw.ca)
Secretary, ISO WG21/ANSI J16 (C++) standards committee (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
C++ community program manager, Microsoft (www.gotw.ca/microsoft)
True, but "that approach" above is about specializations, and they
don't have this problem. Only overloads do.
The biggest problem with the "specializations" approach is that we
don't have partial specialization for function templates, and
therefore, we can't try to use it, get some experience with it, and
determine whether, or to what extent, it solves the "swap problem."
No, the third argument will be an exact match, and the overload
resolution may select the wrong function as "best viable" without even
getting to partial ordering, if I understand the rules correctly. :-)
I guess that my point is there is no automatic policy that eliminates
accidental conformance; we need to rely on programmer discipline. It
might be a mandatory trailing tag; it might be a mandatory prefix
wherever ADL is suspected (my preferred solution since there's one
more instance where a prefix is needed - virtual function overrides
don't respect scopes as well.) And neither policy works for operators.
> > Apart from that, I agree with most of your post. Namespaces and ADL
> > don't create any new problems. They might not _solve_ existing
> > problems as well as some of us would have liked, but they don't make
> > matters worse.
>
> Whether or not the problems are "new" depends on where you start your
> timeline. The problems are no newer than ADL itself.
Namespaces+ADL don't create any new problems compared to no
namespaces, no ADL, everything in ::. The existing practice was to
prefix.
Namespaces without ADL? This breaks operators and templates currently,
doesn't it?
I don't see how. Putting everything in a single namespace can't create
more ambiguities than namespaces + ADL, because ADL doesn't combine
_all_ namespaces in a program into one.
This reads like "Why put operators in namespaces? Because I told you to".
People have learned so much from you, Herb; I know you can do better.
> For the second part, I argued that we need Koenig lookup because, given
C++
> namespaces plus the Interface Principle (which says that free functions
that
> mention a type and are packaged with it in the same namespace are just as
> much part of the type's interface as the type's member functions), we need
> to find those free functions. Koenig lookup lets us find those free
> functions.
...and this reads like a circular argument. The Interface Principle came
after Koenig Lookup, not before. In other words, it is a way of *explaining*
how to function in a universe that includes Koenig lookup. It seems like
you're trying to say that it's a fundamental axiom of programming, but
there's no a priori reason that the IP should hold, and even if there was,
it doesn't imply a need for ADL.
There are lots of different ways to solve the same kinds of problems, and if
you look around at other languages you'll see some of the alternatives. Two
that come to mind are Python and CLOS.
In Python, "free" functions that operate on a user-defined type are
generally packaged in the same module with that type, but no special lookup
magic is needed to find those functions. You simply qualify the function
call:
SomePackage.some_function(some_object)
or you bring the function into the current scope with an import statement:
from SomePackage import some_function
some_function(x)
Simple, direct, and unambiguous. That looks like the IP at work, right? Of
course, you can do this sort of thing in C++ with full qualification and
using-decarations.
Operator overloading in Python uses a mechanism in order to handle multiple
dispatch that I've always considered rather inelegant, but under the
circumstances I don't think it's any worse than introducing ADL into C++ --
it has far fewer far-reaching ugly consequences than ADL does.
In CLOS, multiple dispatch is handled by explicitly saying which function
you want to customize, and CLOS adherents will tell you that this is the
"pure and clean" approach and overloading as in C++ is an abomination (well,
so I was told by one CLOS guy). This is in direct opposition to the
Interface Principle, in which customizations for a given function are
scattered all over the place with no direct connection to any centralized
identity. I bring this up only as a way of arguing that the IP is a fine
tool for understanding how to use C++, but it shouldn't be oversold. In
particular, I don't think it can be used as a justification for any
particular language design choice.
-Dave
Right.
I don't know what "hygenic" means in this context, but I have to disagre
strongly that "bound at the point of defintion" was a good choice. C++
should be moving away from anything with order dependencies. As more
(template/inline) code is written in headers, it gets increasingly hard to
control the order in which things get seen. As I said in another posting,
it's a lot easier to control which definitions make it into a translation
unit than it is to control the order in which they appear.
Eh, wait: my "tag" approach is a *hack*, not a solution. And, as Peter Dimov
pointed out elsewhere even if you remember the tag, it doesn't really
prevent unintended customization (since the tag parameter can be matched by
a user's template parameter), though in general I think it will cause
ambiguity in those cases instead of producing a silent failure.
Anyway, you can't argue with my paragraph you quoted above on the basis of
my tag hack's inadequacies. The paragraph you quoted doesn't even mention
solutions!
So, I'm going to snip the rest of your discussion of the tag hack...
<snip>
> Now for my concession. You're right that the "psychology of a C++
> programmer" makes it such that errors of the kind where you write
> swap(x,y)
> instead of the intended
> std::swap(x,y)
> are way too easy to make. ADL compounds the problem, turning such a
> "slip" into an actual "mistake" when we fail to discover the problem
> because our code appears to work anyway, despite its subtle flaw. Put
> another way, I agree with your overall sentiment here; the language
> design here interacts very badly with the pitfalls of human
> psychology.
Thank you.
> The reason I am arguing this point at all is because I didn't agree with
> how you stated things here:
>
> >If I write code naturally, referring to functions in the same or
> >enclosing namespaces without qualification, I am probably writing buggy
> >code.
>
> I dislike "naturally", because I think it covers up where the problem
> lies. I think that a programmer who really understands C++ would be
> careful about qualifying function names, and thus it's unreasonable to
> assert that "using unqualified names is the _natural_ thing to do"
> (which is how I read your statement).
Try it yourself. I challenge you to write any significant piece of code that
uses templates without falling into the ADL trap. I myself have been
painfully aware of this problem for some time, and I *constantly* find
places where I failed to protect myself from unintended ADL.
> When we focus on what I think is the key point--namely that in this
> case:
> you wrote "swap(x,y)" but you meant to write "std::swap(x,y)"
I know what you're saying, but I think this might be a bad example, because
many people think std::swap is an intended point of customization. The
declaration order dependency makes full qualification for intended points of
customization impractical and error-prone.
> ADL promotes the "slip" into a "mistake" that we're unlikely to notice
> with "working" code--then I think it also helps guide us to finding a
> good remedy for the problem. In this specific example, I don't think
> it's very difficult to find this kind of error in library code, provided
> you design a specific test for it.
Yes, you need to design a specific test for it. Doing that turns out to be
harder than finding the calls which lack qualification in the first place.
> (The easy thing to do is write a
> class which models all of the template concepts necessary to put the
> class in a vector<T> and then sort() it, with the one flaw that it has a
> swap() which can be found only by ADL and whose body prints an error
> message and then exits.
Nope, sort() doesn't have to use swap().
> Try to instantiate the templates with this new
> type. If it fails to compile, you're good. If it compiles, runs, and
> bombs out with your swap error messages, you've made an error. This is
> just a small modification of the technique of using "archetype classes"
> that Jeremy Siek et al use in their "concept checking" methodology.)
> It is annoying to have to write this extra test case, yes.
That's not the point. If you're aware enough to write the test case, you are
aware enough to add the qualification. Furthermore, knowing which test cases
to write depends on knowing the implementation of the algorithm you're
testing, a rather serious pitfall from the maintenance POV - if I change the
implementation of the algorithm I need different tests. In other words, I
have to look at my implementation of sort(), and notice that it's calling
swap() [let's assume for the purposes of this discussion that swap is not an
intended point of customization]. Then either you notice that the call is
qualified, in which case your test is a lot of work for no gain, or you
notice that it's unqualified, in which case you write the test, see that it
fails, add the qualfication, and see that it succeeds. My point is that you
have to do the basic work of not making the mistake (thinking about whether
the call is qualified) in order to come up with the test.
> But it's
> really not hard; especially if the archetype classes which would expose
> these types of errors were provided by a library (e.g. Boost).
Try it first, then tell me it isn't hard to write and maintain this kind of
test.
> My
> impression is that you feel that ADL is overshadowing everything in a
> way that requires a fatalistic outlook on the future of C++, and I don't
> agree with that. (Or maybe I'm just reading too much "melodrama" into
> this discussion thread.)
Actually, I do feel somewhat fatalistic about this particular problem. It
doesn't nullify the many wonderful things about C++, but its negative
effects are sweeping and it seems much too difficult to fix. IMO this is one
of those things which ought to be cleared away as part of the "fix obvious
embarassments" program for C++0x, but it's not even widely recognized as a
flaw.
<snip>
> The point of all of this is that I want you to realize that any
> arguments against ADL are also implicitly arguments for reifying
> template concepts (which hopefully my example above illustrates).
Sorry, I won't agree to that. Language support for Concepts is a great idea,
but ADL is not hitched to it, and I hope it will stay that way. Tying one
ambitious change to another is a sure way to see neither one realized.
-Dave
-----------------------------------------------------------
David Abrahams * Boost Consulting
da...@boost-consulting.com * http://www.boost-consulting.com
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> I think it is theoretically possible, but at a cost in compile
> time and diagnostic quality that may not be acceptable to most
> C++ programmers.
So may I conclude that it was a bad decision to use "<" and ">"
around template arguments?
> Do you have any sort of evidence that compiler vendors fear
> hard work?
No, sorry. See my reply to Gabriel.
Markus
> If it is theoretically possible and pratically impossible, then I would
> say "nearly impossible" and I wouldn't accuse compiler writers to "fear
> hard work and rather pollute C++ language".
>
> And, before going on accusing people in such case, personnally, I would
> ask for and read the minutes of that meeting. Persumably there were a
> debate (i.e. argumentations) followed by a vote. The exact arguments
> (pro and cons) aren't reported here. I think it is bit too harsh to
> accuse people solely based on what David reported (I'm not sayingg he is
> wrong).
Sorry. You are right. I should have taken some more care on how I
formulate such things. Thanks for giving me a reply.
I just had some other discussions in mind about templates
(partial specialization pitfalls) where the kill-all argument
was that the feature
was hard to implement and therefore the
user had to swallow the brain-damage.
The decision at that time was plainly wrong and some famous
compilers even today support the feature in sloppy mode.
I was suspicious this could be the case here again.
>From Daveed's answer I see this seems not to be the case.
OTOH I find it hard to look at this _defect_ of the language,
and hear people say, we must live with it forever.
Anyway, my message was too harsh. Sorry again.
Markus
3. It is more difficult (but still not impossible, mind you) to violate
the ODR under these semantics.
Were overloads found at instantiation time, the same sort<>
instantiation could mean two different things in different translation
units, depending on what overloads are visible.
Herb Sutter:
> In that discussion in my book, I think I addressed that: For the first
part,
> people should put operators in the same namespace as the types they
operate
> on, so "why did people put operators inside namespaces" would be
> "because that's where the types are."
In other words, what you are saying is that this
is the most intuitive place for them to be. Now
intuition is in the eye of the beholder and what
we find to be intuitive behaviour changes as we
learn more and more of the language rules.
My own "journey" is that I found this intuitive and
desirable, at least for operators, when I was starting
out with namespaces. I had pretty much no idea how
lookup worked; it just "did the right thing". Then I
thought more about how normal lookup works and
wondered how these functions were found. So, it
became confusing. Then I learnt about argument-
dependent lookup and it all made sense again. At
this point even the lookup of non-operator functions
using ADL seemed to make a kind of sense. But
then I found more and more places where ADL
behaviour is unintuitive e.g. when disabled by a
member function name, when it creates unintended
ambiguities or when building namespaces with
using declarations. I'm still finding these problems,
although mostly in other people's code now ;-)
So now I think I understand ADL but I don't think its
behaviour as a whole is intuitive.
Generally I'm very much in favour of making the
language more intuitive for novices, but in the
long run ADL does them no favours: the cost is
too high in terms of the complexity that must be
mastered later on. Surely, no regular can have
failed to notice the volume of ADL-related problems
and misunderstandings that pass through this
newsgroup? That in itself should signify that this
is one of the less intuitive areas of the language.
> For the second part, I argued that we need Koenig lookup because,
> given
C++
> namespaces plus the Interface Principle (which says that free
> functions
that
> mention a type and are packaged with it in the same namespace are just
> as much part of the type's interface as the type's member functions),
> we need to find those free functions. Koenig lookup lets us find those
> free functions.
To some degree the Interface Principle explains ADL,
for which I and I'm sure many others are grateful to you,
but to say it justifies it is a step too far.
With global operators instead of ADL-found operators
you could still have an Interface Principle. It would
then read: free functions that mention a type and are
placed in the global namespace are just as much part of
the type's interface as the type's member functions. Is
that really so much harder to grasp?
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
Gabriel Dos Reis:
> | > Surely you don't want to have the content of std::rel_ops in the
> | > global namespace. Do you?
David Abrahams:
> | std::rel_ops should stay safely (and uselessly) buried right where
> | it currently is, or eliminated from the standard library altogether.
Gabriel Dos Reis:
> Certainly, some people have uses of std::rel_ops, so I don't think
> they can be easily eliminated as it is to send a message to a
> newgroup.
> | Anyway, I don't see what that has to do anything, since ADL usually
> | can't find those operators...
Gabriel Dos Reis:
> Please, re-read the text I quoted. I was responding to the question
> as to why people put operators in namespaces.
A clarification: I'm talking solely about those operators
found by ADL. std::rel_ops should be left as it is.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> units, depending on what overloads are visible.
I'm not the expert I wish I were, so I could be wrong here, but I think
this happens anyway. #include's in translation units are often listed
in arbitrary order, so the same definitions (or declarations) may or may
not be present when the template is instanced in different xlation
units.
> As far as I can see, putting operator overloads you wish to be
> globally accessible at global scope usually leads to the same
> ambiguities as putting them in the namespace of one of their
> parameters and letting argument-dependent lookup find them. In some
> cases, in fact, ADL creates more ambiguities (see my example given in
> reply to Sebastian Moleski).
>
> Maybe I misunderstand your point. It would be easier to discuss if you
> provided an example of what you mean.
Consider the example
struct X{
X();
X(int);
};
namespace N1{
struct C1{
C1(int);
};
void operator+(X, C1);
}
namespace N2{
struct C2{
C2(int);
};
void operator+(X, C2);
}
void operator+(X, X);
int main(){
X x;
x+4;
}
This example, as written, is well-formed. If you remove the namespaces
(putting the operators onto global level), the expression 'x+4' becomes
ambiguous. So, putting operators into namespaces avoids ambiguities.
Regards,
Martin
I just wanted to mention that we can "easily" fix operators and
templates in the "namespaces without ADL" case if we simply make some
different assumptions about the language.
For example, here's how to make operators work. I'll use the example of
only operator+, but it generalizes to all the other operators as well
(including the annoyingly-unoverloadable xxx_cast<>() operators, as
well, I think).
Assume that "builtin" to C++ is this declaration in the global
namespace:
template <class Arg1, class Arg2, class Result>
Result operator+( Arg1, Arg2 );
and that there are also builtin specializations, e.g.
template <> // playing the role of Section 13.6 of the standard
int operator+( int, int );
template <>
float operator+( float, float );
etc.
and finally assume that the language says that
x + y
actually means
::operator+(x,y)
That's it.
Now, rather than overload operators, we specialize them. E.g., the
typical example of out-streaming, we do this:
namespace foo {
struct Foo { ... };
}
template <>
std::ostream& operator<<( const ostream& o, const Foo& foo ) { ... }
Now when someone uses the operator, e.g.
std::cout << a_foo;
it's just a call to the global-namespace operator<< template, which we
have specialized.
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
The question was, "why do people put *operators* inside namespaces", not
"why do people bother putting anything in namespaces". If we had
namespaces but no ADL, operators could be placed in the global
namespace. That would create fewer opportunities for ambiguity because
only definitions intentionally placed in the global namespace (usually,
just operators) would have an opportunity to collide.
-----------------------------------------------------------
David Abrahams * Boost Consulting da...@boost-consulting.com *
http://www.boost-consulting.com
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
--
-----------------------------------------------------------
David Abrahams * Boost Consulting da...@boost-consulting.com *
http://www.boost-consulting.com
"Peter Dimov" <pdi...@mmltd.net> wrote in message
news:7dc3b1ea.02072...@posting.google.com...
Excellent point; I take it back. Partial specialization of function
templates would not have a problem with declaration order (thank
goodness!)
> The biggest problem with the "specializations" approach is that we
> don't have partial specialization for function templates, and
> therefore, we can't try to use it, get some experience with it, and
> determine whether, or to what extent, it solves the "swap problem."
I think the bigger problems are:
1. (Very serious) it still requires me to remember to use the
qualification when accessing something in my own scope 2. (Less serious)
we have functions like abs in the standard library which partial
specialization can't address, because they're not templates.
Regards,
Dave
>> hygenic; this is one of the things the committee got absolutely
>> right.
>
>I don't know what "hygenic" means in this context, but I have to
>disagre
"hygienic", I think, actually (I can't spell). It's a term I've seen
used in other communities (e.g. the Scheme macro community) to refer to
constructs which don't capture free names.
>strongly that "bound at the point of defintion" was a good choice. C++
>should be moving away from anything with order dependencies. As more
>(template/inline) code is written in headers, it gets increasingly hard
>to control the order in which things get seen. As I said in another
>posting, it's a lot easier to control which definitions make it into a
>translation unit than it is to control the order in which they appear.
Oof. Ouch. The implications here hadn't occurred to me. It's another
unfortunate side-effect of that darn preprocessor.
It is important to realize that your argument only carries any weight
because of the terribly unfortunate fact that
#include "foo.h"
#include "bar.h"
makes declarations in foo.h visible to bar.h. If we had a sane import
mehanism (one that worked at the language level, rather than the
preprocessing level), we could imagine that
import "foo.h"
import "bar.h"
could be treated as working "in parallel", so that the two do not
interfere with one another.
The "goal" of hygiene is that you can figure out how free names get
bound by only "looking upwards". It hadn't occurred to me that the
#include mechanism in C++ automatically makes the whole concept of
"looking upwards" impossible; the author of bar.h cannot see what
declarations appear "before" his because he doesn't even know of the
existence of "foo.h" and has no foreknowledge that a client is about
to say
#include "foo.h"
#include "bar.h"
Ugh. What a kick in the teeth.
Still, I don't think the fact that #include is "broken" should be an
argument to also break templates. Template hygiene by itself is a
good thing--the fact that #include destroys any hope a library author
has about statically determining how free names are bound ought not be a
reason to water-down templates so they destroy all hope as well. If
anything, we need to fix the import mechanism.
If it's any consolation, you're dragging me into the same fatalistic
view of the future of the language, btw. :)
>Nope, sort() doesn't have to use swap().
Touche. Arguably is swap() is intended to be a "point of
customization", then the standard should probably mandate that sort()
use swap() to move the elements around.
>That's not the point. If you're aware enough to write the test case,
>you are aware enough to add the qualification.
I think this is a little unfair to argue, right after you just basically
said "even if you know what you're doing; try it and I promise you that
you'll discover that you still make a number of accidents". My point
about writing tests is that testing would discover the accidents.
But anyway, let's not pick nits. I think we agree on the general point,
which is that ADL (and, as the discussion grows, a number of other
language features) conspire to make the library author's life much more
difficult and error-prone.
>Furthermore, knowing which test cases to write depends on knowing the
>implementation of the algorithm you're testing, a rather serious
>pitfall from the maintenance POV - if I change the implementation of
>the algorithm I need different tests. In other words, I have to look
>at my implementation of sort(), and notice that it's calling swap()
>[let's assume for the purposes of this discussion that swap is not an
>intended point of customization]. Then either you notice that the call
>is qualified, in which case your test is a lot of work for no gain, or
>you notice that it's unqualified, in which case you write the test, see
>that it fails, add the qualfication, and see that it succeeds. My
>point is that you have to do the basic work of not making the mistake
>(thinking about whether the call is qualified) in order to come up with
>the test.
It sounds like you're arguing against the concept/merit of white-box
testing. (Assuming that, a priori, we have determined whether or not
swap() is a point of customization, then it's just a matter of the
difference between black-box and white-box testing. If swap() is not a
customization point, but you have used it in your implementation, then
it's the responsibility of a white-box test; if swap() is a
customization point, then a black-box test would be sufficient to
discover the error/accident.) But I do see your point in the last
sentence ("My point is that..."), and this does make your earlier
argument a little "less unfair". My point is just that applying a sound
testing methodology, would be, IMO, sufficient to prevent/avoid
accidents.
Nevertheless, let me concede that, whereas each one of the individual
issues is, by itself, not in principle hard to deal with, the sum of all
of the issues does conspire to make the library writer's job be rather a
nightmare. I think you could manage to "write solid library
code" by applying a set of sound methods, but it is a ton of work, and
that work stems directly from crazy language features like ADL.
Put another way, I have been making a number of "in theory" arguments
(when I'm playing devil's advocate I'm allowed to do that :) ), whereas
you are countering with the "in practice" ones, and I do believe you
that, in the end, your practice will trump my theory.
>> The point of all of this is that I want you to realize that any
>> arguments against ADL are also implicitly arguments for reifying
>> template concepts (which hopefully my example above illustrates).
>
>Sorry, I won't agree to that. Language support for Concepts is a great
>idea, but ADL is not hitched to it, and I hope it will stay that way.
>Tying one ambitious change to another is a sure way to see neither one
>realized.
I understand what you're saying, but I want to make it clear that in my
last post I wasn't trying to advance some sort of political agenda. My
point was that, if you hypothetically succeed in convincing people that
ADL is bad, and it's removed from the language, then I think many people
who are currently using ad-hoc (rather than reified-concept-
based) designs will discover that their code no longer works. Without
ADL, I am pretty sure that some template code will be forced to
explicitly recognize template concepts (in the design/implementation of
their templates -- this is regardless-of/orthogonal-to whether or not
there's any new _language_ support for concepts or not). Put another
way, without ADL, we are forced to be explicit about "where the
customization points are" in our designs/implementations, and many of
these "customization points" directly correlate to concepts (e.g.
"swappable").
Removing ADL doesn't need to be tied to language support for concepts.
However removing ADL will cause many of the concepts which have been
lurking (implicitly) in our libraries to be forced up out into the
(explicit) open, if those libraries are still to work in the absence of
ADL. So just be aware of that.
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> > Good point. You're saying that someone can provide a function
> > template with a fully-general argument where my algorithm is passing
> > a tag. Oh, well, at least that reduces the problem to an ambiguity
> > error at worst (if I understand the rules correctly).
>
> No, the third argument will be an exact match, and the overload
> resolution may select the wrong function as "best viable" without even
> getting to partial ordering, if I understand the rules correctly. :-)
You usually understand them a little better than I do; I don't see why
this should be an exception ;-)
> I guess that my point is there is no automatic policy that eliminates
> accidental conformance; we need to rely on programmer discipline. It
> might be a mandatory trailing tag;
Doesn't really work, as you've pointed out.
> it might be a mandatory prefix
> wherever ADL is suspected (my preferred solution
So where's the prefix on your boost::get_pointer() function? ...and
don't tell me the prefix is "get_", please!
I'm only asking in order to illustrate that this may be more painful in
practice than we're actually willing to tolerate.
> > Whether or not the problems are "new" depends on where you start
> > your timeline. The problems are no newer than ADL itself.
>
> Namespaces+ADL don't create any new problems compared to no
> namespaces, no ADL, everything in ::. The existing practice was to
> prefix.
>
> Namespaces without ADL? This breaks operators and templates currently,
> doesn't it?
I'm sure it breaks lots of things. It surely breaks lots of code. We've
carefully constructed a language in which various things depend on other
things working together in particular ways. That's part of the reason
I'm pessimistic about solving this problem -- you can't do it with a
single localized change. To address it for real would require more
intestinal fortitude than I can imagine coming out of our current
standardization process.
-Dave
>"Herb Sutter" <hsu...@gotw.ca> wrote in message
>news:tj50ku447jejblkk4...@4ax.com...
>> In that discussion in my book, I think I addressed that: For the first part,
>> people should put operators in the same namespace as the types they operate
>> on, so "why did people put operators inside namespaces" would be "because
>> that's where the types are."
>
>This reads like "Why put operators in namespaces? Because I told you to".
>People have learned so much from you, Herb; I know you can do better.
But that's not what I said. I said "because that's where the types [they
operate on] are." Interfaces should be kept together. Doing so happens to
have benefits for Koenig lookup.
There is in fact a causal relationship between those two things, ADL and the
IP, but it's not in the direction you next mention:
>> For the second part, I argued that we need Koenig lookup because, given C++
>> namespaces plus the Interface Principle (which says that free functions that
>> mention a type and are packaged with it in the same namespace are just as
>> much part of the type's interface as the type's member functions), we need
>> to find those free functions. Koenig lookup lets us find those free
>> functions.
>
>...and this reads like a circular argument. The Interface Principle came
>after Koenig Lookup, not before.
The IP did not, however, come _because_ of Koenig lookup. I just noticed
after formulating the IP that it happened to also apply to Koenig lookup.
Note the examples in my early articles on the IP that I apply it equally to
C-style OO with handles (e.g., fopen et al., and the C Windows SDK).
> In other words, it is a way of *explaining*
>how to function in a universe that includes Koenig lookup. It seems like
>you're trying to say that it's a fundamental axiom of programming, but
>there's no a priori reason that the IP should hold, and even if there was,
>it doesn't imply a need for ADL.
The IP happens to hold in different languages and contexts, including
C-style OO with handles. Back then, doing OO was a coding pattern; since
then we've gained language support. But it underscores the fundamental
equality of member and (certain) free functions, across many languages, or
at least C-like ones. This is why people like Andrei are quite serious about
favoring a syntax unification where members and nonmembers can be used with
either syntax (i.e., object.func(...) vs. func(object, ...)) which would
greatly simplify some template-writing problems today. Oh, Andrei knows the
notational unification may never happen in Standard C++ itself, but it's a
serious argument quite apart from Koenig lookup or even specific programming
languages, present and future -- for example, I suspect that Andrei is quite
liable to create a language of his own in the next few years and if so I'm
certain this will be reflected in that.
> In Python, [...]
> Simple, direct, and unambiguous. That looks like the IP at
> work, right? Of course, you can do this sort of thing in C++
> with full qualification and using-decarations.
No argument. I do think the IP is wider-ranging than just C++ (or even C)
and certainly more so than Koenig lookup.
> as a way of arguing that the IP is a fine tool for
> understanding how to use C++, but it shouldn't be oversold.
> In particular, I don't think it can be used as a
> justification for any particular language design choice.
As far as I can tell, we're in violent agreement. :-) I didn't have in mind
trying to defend or justify Koenig lookup. My point wasn't to be an
apologist for it, nor to be a critic of it. I did have in mind helping
people to understand it, in the context of teaching a wider-ranging
principle about C++ and other languages.
Remember that, back when the standards committee was working on name lookup,
there was a lot of churn on what the name lookup rules should be. People
kept coming up with new examples that ought to work but didn't under the
draft rules, the rules were tweaked, and people came up with more examples,
and the rules were tweaked again, until finally we ended up where we are.
The result is entirely empirical, albeit an imperfect successive
approximation. My point is that by empirical trial-and-error approximation,
based on a series of code samples, we've come up with a rule that not at all
coincidentally happens to match the IP.
The IP does not exist because of Koenig lookup; rather, Koenig lookup was
iteratively massaged into its current form because of the (then-unknown) IP
which was invisibly underlying the examples they were working with. That is,
people realized that the examples ought to work, even if they couldn't
(then) articulate a fundamental principle about why.
It's because of this that the XC++ material specifically notes that "it's no
accident that the Interface Principle works exactly the same way as Koenig
lookup. Koenig lookup works the way it does fundamentally because of the
Interface Principle."
That doesn't mean that ADL can't be improved, or is free from weirdnesses.
Herb
---
Herb Sutter (www.gotw.ca)
Secretary, ISO WG21/ANSI J16 (C++) standards committee (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
C++ community program manager, Microsoft (www.gotw.ca/microsoft)
Check out "THE C++ Seminar" - Oct 28-30, 2002 (www.thecppseminar.com)
I think we're now nearly in utter, violent agreement. However...
> >> The point of all of this is that I want you to realize that any
> >> arguments against ADL are also implicitly arguments for reifying
> >> template concepts (which hopefully my example above illustrates).
> >
> >Sorry, I won't agree to that. Language support for Concepts is a great
> >idea, but ADL is not hitched to it, and I hope it will stay that way.
> >Tying one ambitious change to another is a sure way to see neither one
> >realized.
>
> I understand what you're saying, but I want to make it clear that in my
> last post I wasn't trying to advance some sort of political agenda. My
> point was that, if you hypothetically succeed in convincing people that
> ADL is bad, and it's removed from the language, then I think many people
> who are currently using ad-hoc (rather than reified-concept-
> based) designs will discover that their code no longer works. Without
> ADL, I am pretty sure that some template code will be forced to
> explicitly recognize template concepts (in the design/implementation of
> their templates -- this is regardless-of/orthogonal-to whether or not
> there's any new _language_ support for concepts or not). Put another
> way, without ADL, we are forced to be explicit about "where the
> customization points are" in our designs/implementations, and many of
> these "customization points" directly correlate to concepts (e.g.
> "swappable").
?
> Removing ADL doesn't need to be tied to language support for concepts.
> However removing ADL will cause many of the concepts which have been
> lurking (implicitly) in our libraries to be forced up out into the
> (explicit) open, if those libraries are still to work in the absence of
> ADL. So just be aware of that.
I don't buy this part of your argument at all. I realize that removing ADL
would break plenty of code. However, I can't think of a single case where
being explicit about an otherwise undeclared Concept could help fix the
breakage. I'd love to see a concrete example of what you're talking about;
it sounds like just another "in theory" argument from the devil's advocate.
-Dave
----------------------------------------------------------
David Abrahams * Boost Consulting
da...@boost-consulting.com * http://www.boost-consulting.com
Which part do you not believe: That the export model was the motivation
for ADL (extended Koenig lookup), or that it is actually needed for the
export model?
The former issue is documented in the committee mailings following
the July 1996 J16+WG21 meeting (Stockholm): The separation model
was definitely the reason that Koenig lookup was extended (and
thereby became what ADL is today). (Your other rationale wasn't
a motivation, though some may see it as an "a posteriori"
rationalization ;-).
The second part (whether it is needed to support export) is
debatable in terms of what "export" actually is. I would agree
that a usable separation model could be obtained without it
(though it would arguably be quite different from "export").
In fact, I suspect a separation model in which unqualified calls
never resolve to functions not visible with ordinary lookup could
be OK. I can imagine a model where only qualified calls with a
dependent qualifier could resolve to declarations in other
translation units (a namespaceof operator comes to mind in that
context, but I don't know it's needed or useful).
Daveed
Neither. This was a discussion about the definition-context binding of
qualified names, not about ADL per se.
-Dave
-----------------------------------------------------------
David Abrahams * Boost Consulting
da...@boost-consulting.com * http://www.boost-consulting.com
"Herb Sutter" <hsu...@gotw.ca> wrote in message
news:cki3kuk86d308d2k7...@4ax.com...
> On 26 Jul 2002 12:07:37 -0400, "David Abrahams" <david.a...@rcn.com>
> wrote:
>
> >"Herb Sutter" <hsu...@gotw.ca> wrote in message
> >news:tj50ku447jejblkk4...@4ax.com...
> >> In that discussion in my book, I think I addressed that: For the first
part,
> >> people should put operators in the same namespace as the types they
operate
> >> on, so "why did people put operators inside namespaces" would be
"because
> >> that's where the types are."
> >
> >This reads like "Why put operators in namespaces? Because I told you to".
> >People have learned so much from you, Herb; I know you can do better.
>
> But that's not what I said. I said "because that's where the types [they
> operate on] are." Interfaces should be kept together.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Not everyone agrees with that premise. When stated like that it sounds like
"operators should be placed in namespaces because interfaces should be kept
together...because I told you so".
> Doing so happens to have benefits for Koenig lookup.
>
> There is in fact a causal relationship between those two things, ADL and
the
> IP, but it's not in the direction you next mention:
Then it doesn't "just happen" to have benefits for Koenig lookup.
Member and free functions are far from "fundamentally equal" in C++. In
Dylan, perhaps, the story is different.
> This is why people like Andrei are quite serious about
> favoring a syntax unification where members and nonmembers can be used
with
> either syntax (i.e., object.func(...) vs. func(object, ...)) which would
> greatly simplify some template-writing problems today. Oh, Andrei knows
the
> notational unification may never happen in Standard C++ itself, but it's a
> serious argument quite apart from Koenig lookup
Yes, quite apart from Koenig lookup. Why is it relevant here, in a
conversation about ADL?
> > as a way of arguing that the IP is a fine tool for
> > understanding how to use C++, but it shouldn't be oversold.
> > In particular, I don't think it can be used as a
> > justification for any particular language design choice.
>
> As far as I can tell, we're in violent agreement. :-) I didn't have in
mind
> trying to defend or justify Koenig lookup. My point wasn't to be an
> apologist for it, nor to be a critic of it. I did have in mind helping
> people to understand it, in the context of teaching a wider-ranging
> principle about C++ and other languages.
Then perhaps you'd like to rephrase your final remark at
http://www.gotw.ca/gotw/030.htm?
To be fair, Koenig Lookup and the Interface Principle are two ways to
express the intention that of those who participated in the design of the
lookup rules.
> That doesn't mean that ADL can't be improved, or is free from weirdnesses.
And it doesn't mean that the IP is fundamental or even an appropriate
approach to behavior customization.
-Dave
-----------------------------------------------------------
David Abrahams * Boost Consulting
da...@boost-consulting.com * http://www.boost-consulting.com
OK, stop there -- that's all that I'm saying, so we agree, the IP is
intuitive. It makes sense to put all the pieces of the interface, member or
free, with the type, right? Good. Full stop.
Deep breath. Having said that, of course, it's one thing to talk about the
IP and quite another to talk about ADL (Koenig lookup) which is another
topic and only comes up in the same breath because it's so closely related
to the IP and motivated by many of the same ideals. But Koenig lookup is an
empirical approximation -- the IP was only stated later by me, and at the
time the lookup rules were invented all the committee had was an iterative
process whereby people would keep pointing out new cases that ought to work,
but didn't, and the rules kept being tweaked until the got closer and closer
to the IP without necessarily being aware of the IP as an underlying
principle. But the approximation is pretty good, at least at the simple "dop
the right thing in the common cases" level:
>I had pretty much no idea how
>lookup worked; it just "did the right thing". Then I
>thought more about how normal lookup works and
>wondered how these functions were found. So, it
>became confusing. Then I learnt about argument-
>dependent lookup and it all made sense again. At
>this point even the lookup of non-operator functions
>using ADL seemed to make a kind of sense. But
Right. And although I'm happy to point out the parallel between the IP and
ADL, and I think the IP is very useful for understanding ADL, I'm not
pretending to be an apologist for ADL (right now). For, as you go on to
point out, in these deep waters there lurketh complexity:
>then I found more and more places where ADL
>behaviour is unintuitive e.g. when disabled by a
>member function name, when it creates unintended
>ambiguities or when building namespaces with
>using declarations. I'm still finding these problems,
>although mostly in other people's code now ;-)
>So now I think I understand ADL but I don't think its
>behaviour as a whole is intuitive.
>To some degree the Interface Principle explains ADL,
>for which I and I'm sure many others are grateful to you,
>but to say it justifies it is a step too far.
In the sense that I believe the IP underlies the successive examples that
led to successive refinement and iterative approximation of the lookup rules
that we know today as ADL. It's still, alas, an approximation.
>With global operators instead of ADL-found operators
>you could still have an Interface Principle. It would
>then read: free functions that mention a type and are
>placed in the global namespace are just as much part of
>the type's interface as the type's member functions. Is
>that really so much harder to grasp?
Nope, it's a question of the IP plus namespaces vs. IP plus name mixing (I
stop short of saying namespace pollution). It is certainly possible to throw
everything into the global namespace, but there is still ongoing discussion
about whether this is better or worse than C++ namespaces.
Herb
---
Herb Sutter (www.gotw.ca)
Secretary, ISO WG21/ANSI J16 (C++) standards committee (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
C++ community program manager, Microsoft (www.gotw.ca/microsoft)
Check out "THE C++ Seminar" - Oct 28-30, 2002 (www.thecppseminar.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Herb Sutter:
> OK, stop there -- that's all that I'm saying, so we agree, the IP is
> intuitive. It makes sense to put all the pieces of the interface, member
or
> free, with the type, right? Good. Full stop.
Not quite. I'm saying I found this intuitive, for operators
only, when I started out, but that I don't now. At this novice
point I didn't find the IP intuitive for non-operators. That only
came later, was based upon an incomplete understanding
of ADL, and was relatively short-lived.
> Deep breath. Having said that, of course, it's one thing to talk about the
> IP and quite another to talk about ADL (Koenig lookup) which is another
> topic and only comes up in the same breath because it's so closely related
> to the IP and motivated by many of the same ideals.
Prior to your post, Herb, we were discussing ADL, not IP...
[snip]
Garry Lancaster:
> >With global operators instead of ADL-found operators
> >you could still have an Interface Principle. It would
> >then read: free functions that mention a type and are
> >placed in the global namespace are just as much part of
> >the type's interface as the type's member functions. Is
> >that really so much harder to grasp?
Herb Sutter:
> Nope, it's a question of the IP plus namespaces vs. IP plus name mixing (I
> stop short of saying namespace pollution). It is certainly possible to
throw
> everything into the global namespace, but there is still ongoing
discussion
> about whether this is better or worse than C++ namespaces.
I think you misunderstand. I *like* namespaces and I think
most things should go in them. I just don't think they are the
right place for functions we wish to be globally accessible
using unqualified names and without employing either using
declarations or using directives. Whilst putting globally
accessible operators inside the namespace of their types may
seem intuitive to us at some points on the learning curve, this
perception is based upon an incomplete appreciation of the
necessary complexities of any such system. (And perhaps also
an incomplete appreciation of the ability of overloading to resolve
name clashes.)
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
> The question was, "why do people put *operators* inside namespaces", not
> "why do people bother putting anything in namespaces". If we had
> namespaces but no ADL, operators could be placed in the global
> namespace. That would create fewer opportunities for ambiguity because
> only definitions intentionally placed in the global namespace (usually,
> just operators) would have an opportunity to collide.
Please see my example in <j4r8hqs...@informatik.hu-berlin.de> how
moving operators into global namespace adds ambiguities.
Regards,
Martin
Martin v. Lwis:
> Consider the example
>
> struct X{
> X();
> X(int);
> };
>
> namespace N1{
> struct C1{
> C1(int);
> };
> void operator+(X, C1);
> }
>
> namespace N2{
> struct C2{
> C2(int);
> };
> void operator+(X, C2);
> }
>
> void operator+(X, X);
>
> int main(){
> X x;
> x+4;
> }
>
> This example, as written, is well-formed. If you remove the namespaces
> (putting the operators onto global level), the expression 'x+4' becomes
> ambiguous.
> So, putting operators into namespaces avoids ambiguities.
This doesn't follow: you can't generalise from a single
example, especially since I already posted a counter-example.
In any case, it over-simplifies the suggestion, which is to
place operators that currently rely on ADL in the global
namespace, not to place *all* operators there. In your example,
the operators are doing "double duty": for X + C1 and
X + C2 we want them to be found by ADL, for X + int we
certainly do not. Giving each function a single responsibility
will solve the problem. To my mind, the resulting program
will be easier to understand also.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
Well, I'll do my best. This is another "made up" example, because
offhand I'm not familiar enough with any particular template library
which is relying on ADL to work; I can only imagine that examples
similar to this are, indeed, out there. I've chosen what appears to me
to be a plausible domain. I hope you won't take my personal lack of
exposure to real-world examples as invalidating the point of my
argument (as the argument is more general than any particular example).
Enough disclaimers... here we go.
Suppose we have some kind of numeric algorithms library which relies on
numbers in some fundamental way. Rather than fixing the representation
type to be "double" or "float" or "my_library::BigNum" or whatever, the
number type is a template parameter to all of the algorithms in the
library. In this way, the library lets the client select the
appropriate representation type, and the library will work provided
that the chosen representation type (T) has a certain interface (which
includes, for instance, "T operator-(T,T)" and "T abs(T)" and other
functions along these lines).
(Note that the constraints that the library places on the number-
representation-type parameter T form a "concept" which we might label
"Numeric"; T is required to have both (a) the right "shape" so that code
like
// assume: T x, y, z;
z = x - y;
y = abs(x);
will compile, and also (b) the right semantics for those functions, in
that "-" computes a difference, "abs" computes the absolute value,
etc. Given these constraints, the library will behave "as
advertised". The "Numeric" concept exists only implicitly in the
library code, or at best, explicitly in its documentation.)
The library exports a number of algorithms for computing various stuff
(like, I dunno, Newton's method or something). We can imagine that one
of the functions in the interface to the library looks something like
template <class T> // T models Numeric
T some_solver_alg( /* some other various params */,
T interval_bottom, T interval_top,
T tolerance ) {
while( abs( interval_bottom-interval_top ) > tolerance ) {
// yadda yadda, compute successive approximation
}
return interval_top;
}
The call to abs() will depend on ADL to work; the library is designed so
that a client could write
#include "the_library.h"
namespace client {
struct BigNum {
// yadda yadda, including
BigNum operator+( const BigNum& rhs ) const { ... }
BigNum operator-( const BigNum& rhs ) const { ... }
// etc.
};
BigNum abs( const BigNum& x ) { ... }
// etc., so that BigNum meets the constraints of "Numeric"
}
int main() {
client::BigNum x(0), y(1), z;
...
z = some_solver_alg( /*yadda*/, x, y, client::BigNum(0.0001) );
}
and things would work. Okay so far?
Now, tomorrow morning the C++ revolutionists win the namespace-
protection wars, and ADL is banished from the kingdom of C++ (and my
own indulgent metaphor begins). :)
The library no longer works, so the library authors try to decide how
to fix their code. Specifically, the call to abs() is troublesome.
How can they call the user's abs() function, which lives in an unknown
namespace?
(Many readers might now suggest qualifying the call to abs() with
something like "namespaceof(T)::abs()"; this is indeed another
possiblility, but I'll later argue why it's not my favorite one.)
After some thought, the library designers realize that the problem is
solvable if they reify the Numeric concept into an actual named
abstraction in the C++ code:
// inside the_library.h
// (There's more than one way to reify a concept; here I choose an
// approach based on namespaces.)
namespace Numeric {
template <class T>
T minus( const T& x, const T& y ) {
return x-y;
}
// ...
template <class T>
T abs( const T& x );
template <> double abs( const double& x ) { return std::abs(x); }
template <> float abs( const float& x ) { return std::abs(x); }
}
// ...
template <class T>
T some_solver_alg( /* some other various params */,
T interval_bottom, T interval_top,
T tolerance ) {
while( Numeric::greater( Numeric::abs(
Numeric::minus(interval_bottom,interval_top) ), tolerance ) ) {
// yadda yadda, compute successive approximation
}
return interval_top;
}
This library now works with any client type which supports the named
operations in namespace Numeric. Some of these operations will work
automagically; for example, if '-' is implemented as a member function,
the "default implementation" of Numeric::minus provided by the library
authors will work fine. Other operations require the client to create
explicit specializations. The new client code is:
#include "the_library.h"
namespace client {
struct BigNum {
BigNum operator+( const BigNum& rhs ) const { ... }
BigNum operator-( const BigNum& rhs ) const { ... }
};
BigNum abs( const BigNum& x ) { ... }
}
// *** This part is new ***
namespace Numeric {
template <> client::BigNum abs( const client::BigNum& x )
{ return client::abs(x); }
}
int main() {
client::BigNum x(0), y(1), z;
...
z = some_solver_alg( /*yadda*/, x, y, client::BigNum(0.0001) );
}
And it works. Calls that used to be found via ADL can now instead be
found by routing them through a qualified call to a template function
--a template function whose name corresponds in some one-to-one fashion
with the applicable concept, and that can be (perhaps partially)
specialized for arbitrary client-defined data types which intend to
conform to the concept.
Indeed, in libraries throughout the land which had previously relied
upon the now-banished ADL, the library authors discovered that the
technique of concept-reification always provided a workable alternative
to ADL in order to find the right function body to call. Previously
implicit concepts began manifesting themselves explicitly in C++
library code; these manifestations provided a named static point of
indirection for various forms of customization. Both library authors
and clients alike were delighted to discover that the dynamic
components of this indirection (that is, the tiny function bodies such as
namespace Numeric {
template <> client::BigNum abs( const client::BigNum& x )
{ return client::abs(x); }
}
in the example above) were absent from their run-time executables, due
to the foresight of the Compiler Implementers--one of the larger clans
in the kingdom who had previously used their power in the kingdom to
pass various laws which said things like "references require no storage"
and other laws with exotic names like "return value optimization".
On the fringes of the kingdom, the speakers of backwards-English
received an unexpected benefit. For years these people had felt
marginalized by the rest of the C++ community, by virtue of the fact
that they spoke a different language dialect. Specifically, they had
been writing their own code like
namespace tneilc {
struct muNgiB {
muNgiB operator+( const muNgiB& rhs ) const { ... }
muNgiB operator-( const muNgiB& rhs ) const { ... }
// ...
};
muNgiB sba( const muNgiB& x ) { ... }
// ...
}
which looks perfectly natural to other backwards-English speakers.
However they had found that their client code always failed to interact
well with mainstream C++ libraries due to petty disagreements about
names. They were always forced to pollute their own namespaces with
unusual names like "abs" which just implemented the same functionality
as their "sba" function, in order to successfully interface their own
code with existing mainstream libraries like "the_library.h". They
always felt bitter about this--that for some reason, the constraints
required by other libraries was somehow forcing them to change the
names they provided within their own namespaces--but they had written
it off as a necessary price to pay for interoperability.
With the downfall of ADL and the rise of the concept-reification
movement, the backwards-English speakers discovered that they no longer
needed to pollute their own "tneilc" namespaces with names like "abs".
Their own code could now interface with "the_library.h" simply by adding
namespace Numeric {
template <> tneilc::muNgiB abs( const tneilc::muNgiB& x )
{ return tneilc::sba(x); }
}
The names "Numeric and "abs" still looked foreign to them, but they were
pleased now that this ugly code was no longer necessary within their own
namespace. Somehow the new placement of the function which bridged the
"naming-convention gap" between the two communities seemed more fairly
located. Interoperability no longer required polluting their own
namespaces with strange names just for the sake of convention; rather
specializations of functions with existing names could mediate the
naming-gap and create the indirection. (As an aside, notice that even
if they had chosen to provide named functions like "sunim" rather than
"operator-", an appropriate specialization of Numeric::minus would
mediate that name conflict as well.)
Yadda yadda, and they all lived happily ever after. :)
Ok, more seriously, I hope this illustrates the point I was trying to
make.
I need to go back now, and address the issue of the alternative
solution based on namespaceof(). If "the_library.h" changed its calls
from "abs(x)" to "namespaceof(T)::abs(x)", then the original "client"
example would work without change. Effectively this solution just
applies ADL "manually". It is not clear to me if this solution can
generalize to functions of more than one argument (which may be
supplied by types from more than one namespace). On the other hand,
the concept-reifying solution does generalize to multiple arguments,
types, and namespaces.
Furthermore, the namespaceof()-based solution doesn't address the
complaints of the backwards-English. This is a "real" problem that I
think is often overlooked. David, you're arguing in favor of getting
rid of ADL in order to restore "namespace protection". It is
worthwhile to note that the existing designs of many libraries erodes
the namespace protection of _clients_, as illustrated by the
backwards-English. Many libraries require that, in order to
interoperate with client-defined types, certain names _chosen by the
library authors_ must appear in the client namespace ("abs" in the
example). Effectively there's namespace pollution working in the
opposite direction. A well-intentioned client may create a huge code
base in namespace "foo" which includes types named "Foo" and functions
that operate on Foos named "f" and "g". At some point in the future,
the client discovers that a new template library has been published,
which just happens to require types which enjoy the same semantic
properties that Foos do. The client downloads the library, eager to
reap the benefits that the library provides to all things Foo-like.
But, to the client's dismay, he discovers that, by convention, the
library requires that f(someFoo) and g(someFoo) implement certain
behaviors--those behaviors provided by the client's own g() and f()
methods, respectively. Due to the lack of prior agreement on naming,
the client is now faced with the prospect of renaming "f" to "g" and
vice-versa throughout his code base, in order to interoperate with the
library. If the library was designed and implemented based on
concept-reification rather than ADL, there would be no such naming
conflict.
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I know that this is of topic for this thread but I want to get the idea
down while I remember it. Suppose that we allowed this syntax:
class X {
// private and protected interfaces
export public:
// public interface
};
With 'export public' meaning that this part of the definition shall
behave as if forwarding functions are provided in the enclosing
namespace with an added first parameter that is a X&, or X const & as
appropriate.
--
Francis Glassborow ACCU
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
On 19 Jul 2002 05:19:11 -0400, "Sebastian Moleski" <s.mo...@tcu.edu>
wrote:
>Similarly, "int f(int())" will declare a function f that takes one
>argument of type "function with no arguments returning int" and that
>returns an int. "int f((int())" is a variable f of type int intialized
>with a default-initialized int. Now, you might think that nobody writes
>stuff like this, but trust me on that people do (usually not with int's
>though, and usually expecting the result of the latter with the syntax
>of the former).
Well, this is in part because many (other) people hastily teach you to
read a C++ declaration: "start from the identifier... go to the right...
do you find a left parenthesis? ok... it's function that takes... etc.
etc."
BTW, question to language lawyers: is there any reason (templates?) why
we have abstract declarators of function type? That is, considering that
the effect of declaring
int f ( int () );
is the same of
int f (int (*) () );
why allowing the former ?
Genny.
Indeed, that is the true problem here. We cannot really blame
this on ADL, IMO.
Daveed
There are corner cases that still cause problems, but it does decrease
the possibility of collisions.
> > it might be a mandatory prefix
> > wherever ADL is suspected (my preferred solution
>
> So where's the prefix on your boost::get_pointer() function? ...and
> don't tell me the prefix is "get_", please!
It doesn't have a prefix, and this is (now) a deliberate design
decision. So sue me, to quote a popular C++ technical argument. :-)
When a library decrees that it uses an unqualified call to 'id', this
is an attempt to establish a de-facto standard on what 'id' means,
regardless of scope. (When this library is the standard library, the
standard is de-jure, i.e. 'swap' means 'std::swap', regardless of
scope.)
Since 'get_pointer' is now both a service needed by
boost::mem_fn/boost::bind, _and_ an official part of the boost smart
pointers interface, I have decided that it deserves to stay
unprefixed.
> I'm only asking in order to illustrate that this may be more painful in
> practice than we're actually willing to tolerate.
No, get_pointer's lack of prefix is not caused by "painfulness." When
it was first defined, the lack of prefix was a mistake; I (we) didn't
really understand the lookup issues well enough. Now it's a feature.
My rules are: every documented unqualified call should, by default,
use a prefix (intrusive_ptr_add_ref) unless decided otherwise; every
undocumented unqualified call is a bug, if it causes problems in
practice.
There is a subtle difference; specializations don't do implicit
conversions, so if you derive something from Foo, an overloaded
operator will still work with it, but a specialization will not.
>
> namespace A {
> struct B { };
> template<int X> void f(B);
> }
>
> namespace C {
> template<class T> void f(T t);
> }
>
> void g(A::B b) {
> f<3>(b); // ill-formed: not a function call
> A::f<3>(b); // well-formed
> C::f<3>(b); // ill-formed; argument dependent lookup
> // applies only to unqualified names
The comment above has to be corrected because
C::f<A::B>(b); // well-formed
I.e. C::f<3>(b) is ill-formed just because we can't specialize
template<class T> f with constant 3: C::f<> needs a typename.
> using C::f;
> f<3>(b); // well-formed because C::f is visible; then
> // A::f is found by argument dependent lookup
> }
So the example in 14.8.1 is much more confusing than it was stated by DR241.
--
With all respect, Sergey. http://cpp3.virtualave.net/
mailto : ders at skeptik.net
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
FWIW2, I'm going to be on vacation soon, so I won't be able to respond to
critiques for some time.
FWIW3, the paper contains several references to export. I've learned a lot
more about export since the meeting. If there are inaccuracies, I apologize
in advance.
-Dave
--
David Abrahams
2002/04/24
Proposal for new Namespaces and Lookup Rules
--------------------------------------------
This proposal describes several problems with the current
implementation of namespaces and name lookup rules, and outlines one
way to solve those problems. One aim of the proposed solution is to
maintain backward-compatibility while providing a "transition plan"
for developers (and even standard library implementors) wishing to use
the new facility.
Why Bother?
-----------
Before the introduction of namespaces to C++, it was a common practice
to apply "prefixes" to all names with external linkage, in an attempt
to avoid unpredictable collisions between code in applications and the
libraries they linked to. As long as the prefixes were reasonably
long, this practice worked reasonably well. The major disadvantage was
aesthetic: there was no way to avoid using the prefix within user code
or even within the context of the library's own definition.
Namespaces allowed users and library writers a way to partition the
set of all names so that the use of qualification (the namespace
mechanism's replacement for prefixing) could be avoided for names
defined in local or enclosing contexts. Via using-declarations and
using-directives, we also provided a mechanism for selective use of
unqualified names from other contexts, thus combining convenience with
protection.
This mechanism works well for types, however, due to the liberal way
that argument-dependent lookup is applied, the replacement of
prefixing by namespaces fails to deliver the convenience and
protection seemingly promised even for ordinary function calls. A
conscientious programmer must resort to qualifying calls to functions
in her own namespace if the any argument could come from any other
namespace:
Example 1:
// main.cpp:
#include "lib1.hpp"
namespace user
{
void f(lib1::class1);
void g()
{
lib1::class1 x;
f(x); // call to user::f(x) intended, but might be ambiguous
}
}
In the example above, the potential for damage is limited due to the
fact that library headers change infrequently, but for functions
defined in header files, the situation can be much more volatile:
Example 2:
// lib1.hpp
#include "lib2/part1.hpp"
namespace lib1
{
void f(lib2::base);
inline void g(lib2::derived x)
{
f(x); // might call lib2::f(x)
}
}
The additional problem in example 2 is that lib1.hpp may be #included
in many different contexts in a user's program. Even if lib2 never
changes, the another of lib2's headers may expose a function
lib2::f(lib2::derived) which can be seen by lib1::g() depending on its
context. The result is that even for a single version of lib1 and
lib2, the author of lib1 might not uncover the problem during testing.
Where function templates are involved, the problem is particularly
acute.
Example 3:
// lib2.hpp (no #includes)
namespace lib2
{
template <class T>
void f(T);
template <class T>
void g(T x)
{
f(x); // might call any f defined in the namespace of T
}
}
According to 14.4.6.2, which function are to be considered candidates
to be called by f depends on what's visible at g's point of
instantiation, and if two translation units resolve f differently for
a given T, the behavior is undefined. Controlling visibility is
difficult in C++ due to separate compilation, so much so that one of
the most controversial and hard-to-implement features (export) was
introduced to deal with the problem.
It is worth noting, however, that export only helps to deal with the
resolution of qualified function names, which is arguably a much
smaller problem. Consider:
1. In order to ensure predictable overload resolution, namespace
authors are likely to expose all overloads of one name in a
given namespace which can accept the same number of arguments
from the same header file.
2. It is usually possible for the author of a generic library to
ensure that all overloads of a given function from a single
known namespace are seen, by #including the appropriate headers,
before a function template is defined, but it's impossible to
ensure that the same set of overloads in *all* namespaces are
visible before a function template is instantiated unless you
control all the instantiations.
Furthermore, library writers are unlikely to invite users to overload
functions in the library's namespace, since:
a. It is syntactically heavy and inconvenient for users to define
overloads in other namespaces:
// user.hpp
namespace user
{
class my_class;
} // * Extra commented lines required to
// * produce overloads in lib:: and
namespace lib // * return to user::. Moves f() away
{ // * from my_class.
void f(user::my_class) // also explicit qualification of my_class
} // *
// *
namespace user // *
{ // *
b. Qualified lookups of names from function templates are subject
to dangerous order dependencies, since qualified names bind at
the point of use.
// lib.hpp
namespace lib
{
template <class T> void f(T);
template <class T> void g(T x)
{
lib::f(x); // binds only to *visible* fs
}
}
// user.cpp
#include "lib.hpp"
#include "user.hpp" // overload is too late; f already bound!
int main()
{
user::my_class x;
lib::g(x); // calls lib::f(), not user::f()!
}
Export solves problem b. by neutralizing user-defined names in the
library namespace as a customization technique, so the set of names
found by the library's qualified lookups can be determined by its
vendor when the exported template is compiled. However, though
point-of-use binding was intended to provide syntax checking at
definition time, it can only be applied for dependent names
retroactively, after the template is actually instantiated. Ignoring
the part of the instantiation context which follows the definition
context is a burden for implementors and doesn't serve users; the core
working group is talking about removing this special-case behavior. In
any case, a user has to go out of her way to add definitions to a
library's namespace, so export seems to protect only against malicious
attacks.
Compared with the situation before namespaces, however, we seem to
have opened code up to unintentional attacks. In a world of prefixed
names, a library writer's own test code would be unlikely to compile
correctly if a prefix were ommitted, so once the choice to use
prefixes was made, the compiler would enforce qualification. Today, a
library author must exercise extreme vigilance to be sure that, except
where she explicitly intends to create a point of customization, all
calls whose arguments could come from any other namespace are
qualified. Since unqualified calls are perfectly legal, she gets no
support from the compiler. Since they are perfectly easy, and will
pass all but the most sadistic tests, there is little incentive other
than her good conscience to add qualification. These errors are the
sort that show up only after libraries are deployed.
Because of problem (b) above we have today only one
reasonably-convenient way to allow customization of algorithms in
generic libraries: libraries must call the customizable algorithm
*without* qualification, and must declare explicitly which names are
being used that way by which functions; users must take care to avoid
defining these names in their own namespaces except where the
intention is to customize a given library. If two library implementors
happen to choose the same function name as a point-of-customization
with different semantics, the upshot is at best confusing: the user
may need to create two overloads of the same name in his own namespace
with different semantics. Since the name is the same, it's quite
likely that the semantics will be similar, but not identical. In the
worst case, the functions have identical signatures, and the libraries
simply refuse to interoperate in the user's application.
A Solution
----------
The solution I'll propose here centers around a different kind of
namespace with new lookup rules, on the assumption that breaking
backward-compatibility of existing namespaces is unacceptable. I would
dearly love to find a solution which didn't involve major language
changes, but I don't see any alternative which allows the compiler to
help library authors prevent unintended argument-dependent
lookups. The new entity is provisionally called a "qualified
namespace", and is declared by following the namespace name with "::",
as follows:
namespace new_std::
{
// declarations and definitions
}
The lookup rules in a qualified namespace differ from those in an
old-style namespace as follows:
1. By default, argument-dependent lookup does not take effect for
arbitrary unqualified calls.
2. Dependent name lookups which do not use argument-dependent lookup
must match some declaration at template definition time, but
binding to actual function implementations occurs at instantiation
time:
namespace new_std::
{
template <class Iterator>
void iter_swap(Iterator x, Iterator y)
{
swap(*x,*y); // error: no swap defined
}
template <class T> void swap(T&, T&);
template <class Iterator>
void sort(Iterator start, Iterator finish)
{
...
swap(*a, *b); // OK: the swap above could match
...
}
}
* Binding at instantiation time removes the order-dependencies that
motivated export and restores overloading in an algorithm's
namespace-of-definition to viability as a customization
technique.
* Overloading in the algorithm's namespace solves the
library interoperability problems implied by asking users to
provide overloads for each library in her own namespace.
* Requiring a match at definition time allows template definitions
to be syntax-checked before instantiation. Since nobody will be
using argument-dependent lookup by mistake in a qualified
namespace, it should be possible in practice to check syntax much
more thoroughly in a qualified namespace than in an unqualified
one.
3. In order to make generic library customization convenient, I
propose to allow "remote" definition of previously-declared
names.
namespace newstd::
{
template <class T> class complex;
template <class T> void swap(T&,T&);
}
namespace user
{
class fixedpoint
{
...
};
// Specialize newstd::complex
template <> class newstd::complex<fixedpoint>
{
...
}
void newstd::swap(fixedpoint&, fixedpoint&); // OK
// illegal by current rules; unspecialized template not known
template <> class newstd::vector<fixedpoint>;
// illegal by new rule: no known iter_swap() function in newstd::
void newstd::iter_swap(fixedpoint&, fixedpoint&);
}
For function overloads, a declaration of a function with the same
number of arguments in the target namespace must already be
visible:
namespace user
{
void newstd::swap(fixedpoint&, fixedpoint&, int); // illegal
}
The same technique applies to the "Barton & Nackman trick",
allowing it to be used to provide customizations for qualified
namespaces:
namespace user
{
class fixedpoint
{
...
friend fixedpoint newstd::math::sin(fixedpoint)
{
...
}
};
}
Friend functions defined in this way would not be subject to the
usual restrictions which prevent them from being found other than
through argument-dependent lookup.
4. In order to provide a migration path from old-style to qualified
namespaces, argument-dependent lookup can be enabled for *specific*
names through the use of an "unqualified using-declaration":
namespace new_std::
{
using swap; // allows argument-dependent lookup of "swap" from
// within new_std
template <class Iterator>
void sort(Iterator start, Iterator finish)
{
...
swap(*a, *b); // uses argument-dependent lookup
...
}
}
The "unqualified using-declaration" can also be used within a the
function template definition, further limiting the scope of its
effect:
namespace new_std::
{
template <class Iterator>
void sort(Iterator start, Iterator finish)
{
using swap; // allows argument-dependent lookup of "swap" from
// within new_std::sort
...
swap(*a, *b); // uses argument-dependent lookup
...
}
}
5. What about the operators? It is my preference to treat uses of
operators within a qualified namespace the same as we treat any
other function. Argument-dependent lookup could be explicitly
enabled through the use of unqualified using-declarations:
template <class T>
T square(T x)
{
using operator*;
return x * x;
}
Those seeking to conveniently enable argument-dependent lookups for
all operators within a qualified namespace could easily create a
header file which does so:
namespace mymath::
{
#include "using_ops.hpp"
}
Implications for Namespace std and Other Libraries
--------------------------------------------------
Currently there seems to be momentum towards allowing algorithms in
namespace std to call other selected algorithms, most notably swap(),
without qualification so they can be found via argument-dependent
lookup. If we follow that course, implications for std are minimal,
and authors of generic libraries in other namespaces must always add a
using-declaration to bring the generalized algorithm into scope at the
point of use:
namespace lib1
{
template<class Iterator>
void lib1_permutation(Iterator start, Iterator finish)
{
...
using std::swap;
swap(x, y);
...
}
}
It is worth noting that when a generalized version of an algorithm
exists, as in the case of swap, that algorithm *is* effectively
associated with a namespace. The using-declaration becomes a clumsy
form of qualification which indicates the algorithm's home.
If we choose to follow the course of using argument-dependent lookup
to allow customization in namespace std, the proposal above at least
allows implementors to migrate the standard library to a safer qualified
namespace without breaking backward compatibility. The list of names
and call contexts where argument-dependent lookup takes effect could
simply be encoded in unqualified using-declarations.
If we choose *not* to allow argument-dependent lookup in namespace std
(my preference), std could still become a qualified namespace, and
implementations could drop their qualification of calls to algorithms
like swap. We could grant permission to create overloads in std::
consistent with the standard versions of the same function, allowing
users to customize exactly the intended standard library
algorithm. Other libraries would call standard algorithms with
qualification as usual, and would not need to rely on
argument-dependent lookup to resolve the calls.
Conclusion
----------
The future of C++ lies in its power to make great libraries, so the
issues of library interoperability, customization, and robustness must
be taken seriously. If we compare the C++ standard library with the
libraries of languages like Java or Python, it's easy to see that the
potential to increase the number of namespaces competing for an
unqualified name is enormous. This proposal puts the compiler's static
checking at the service of library authors and allows users to
conveniently and precisely express the notion of algorithm
customization.
> This doesn't follow: you can't generalise from a single
> example, especially since I already posted a counter-example.
I believe that this example is flawed. Your example was
#include <iostream>
namespace ns
{
std::ostream& operator<<(std::ostream& os, int i)
{ os << "ns::operator<<\n"; return os; }
void call_op()
{ std::cout << 1; }
}
int main()
{ ns::call_op(); }
and you were arguing that this produces ambiguities because of
ADL. This is not the case: the conflict is between ns::operator<< and
std::ostream::operator<<(int), i.e. between a method and a global
operator. Even without ADL, members of the operands would be
considered in overload resolution, so this gives a conflict even
without ADL.
So, this is not a counterexample.
Regards,
Martin
Yes, this happens anyway, because different declarations may be
present when the same template is _defined_ in different translation
units. It is possible, in theory, to avoid this problem under the
current rules by using exported templates that have a single point of
definition.
<snip very cute story>
>
> Yadda yadda, and they all lived happily ever after. :)
That's a very cute (though very long-winded) story, but concept reification
played no role in any solution arrived at there, so it doesn't do much to
prove your point. Besides that, you have been arguing that no language
support is needed; just being explicit about concepts would solve some
problem. However, your story mentions concepts only in the context of
language support.
Finally, to get right to the point: you've got it inside out. Take the
std::copy() algorithm, for example. The applicability of std::copy() depends
on the value type of its arguments fulfilling the CopyConstructible concept,
yet std::copy() is never mentioned in the concept requirements for
CopyConstructible. Two different library authors can legitimately each
choose his own semantics for a three-argument function called copy() which
each accept iterators whose value_type is CopyConstructible. Using concepts
during overload resolution can't help us distinguish one from the other.
-----------------------------------------------------------
David Abrahams * Boost Consulting
da...@boost-consulting.com * http://www.boost-consulting.com
> Markus Werle <numerical....@web.de> wrote:
>> Daveed Vandevoorde wrote:
>>
>>
>> > I think it is theoretically possible, but at a cost in compile
>> > time and diagnostic quality that may not be acceptable to most
>> > C++ programmers.
>>
>> So may I conclude that it was a bad decision to use "<" and ">"
>> around template arguments?
>
> Indeed, that is the true problem here. We cannot really blame
> this on ADL, IMO.
Then this may be partly resolved as proposed by Brian McNamara
x.template f<3>(b);
Markus
We're not connecting here, and I'm not sure why. (I think maybe you
think that my story depend on the language knowing about concepts to
resolve overloads, but it totally doesn't.)
No language support is needed; in the story example, simply declaring
namespace Numeric {
template <class T> T abs(const T&);
}
and then having clients specialize it for their T types is sufficient.
This requires no extra language support. (In the cases where there are
multiple arguments and we may need partial specialization of function
templates, which C++ doesn't have, this is easily worked around by
having the function template "forward" the call to a static member of a
class template, and then having clients partially specialize the class
template.)
>Finally, to get right to the point: you've got it inside out. Take the
>std::copy() algorithm, for example. The applicability of std::copy() depends
>on the value type of its arguments fulfilling the CopyConstructible concept,
>yet std::copy() is never mentioned in the concept requirements for
>CopyConstructible. Two different library authors can legitimately each
>choose his own semantics for a three-argument function called copy() which
>each accept iterators whose value_type is CopyConstructible. Using concepts
>during overload resolution can't help us distinguish one from the other.
I don't understand what you mean here.
>The applicability of std::copy() depends on the value type of its
>arguments fulfilling the CopyConstructible concept, yet std::copy() is
>never mentioned in the concept requirements for CopyConstructible.
Of course not. The concepts "come before" the functions which depend on
those concepts. Analogously, in my example, in the definition of the
"Numeric" concept, I didn't mention anything about "some_alg"; indeed,
the creator of Numeric doesn't even know that in the future someone will
implement "some_alg" depending on the Numeric concept.
>Two different library authors can legitimately each choose his own
>semantics for a three-argument function called copy() which each accept
>iterators whose value_type is CopyConstructible. Using concepts during
>overload resolution can't help us distinguish one from the other.
Two different library authors could implement a function named some_alg
which accept arguments of type T that are Numeric. Concepts aren't used
during overload resolution at all. If I import two libraries:
// lib1.h
#include "numeric.h"
namespace lib1 {
template <class T>
T some_alg( /*yadda*/, T bot, T top, T tol ) { ... }
}
// lib2.h
#include "numeric.h"
namespace lib2 {
template <class T>
T some_alg( /*yadda*/, T bot, T top, T tol ) { ... }
}
then the only way I can distinguish between the two is something like
#include "lib1.h"
#include "lib2.h"
...
lib1::some_alg( /*yadda*/, x, y, z );
lib2::some_alg( /*yadda*/, x, y, z );
where the lib1:: or lib2:: qualifiers specify which library (and thus,
which semantics) I want.
I apologize for being long-winded-and-cute when apparently
succinct-and-clear would have been a better choice; apparently my
previous message didn't communicate my idea very well at all.
--
Brian M. McNamara lor...@acm.org : I am a parsing fool!
** Reduce - Reuse - Recycle ** : (Where's my medication? ;) )
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I don't think it needs correction.
>
> C::f<A::B>(b); // well-formed
>
> I.e. C::f<3>(b) is ill-formed just because we can't specialize
> template<class T> f with constant 3: C::f<> needs a typename.
That's the point of the comment: Only C::f is considered (and
therefore it is ill-formed). ADL does not apply and therefore
does not find A::f.
> > using C::f;
> > f<3>(b); // well-formed because C::f is visible; then
> > // A::f is found by argument dependent lookup
> > }
> So the example in 14.8.1 is much more confusing than it was stated by DR241.
Daveed
Martin v. Löwis:
> > > So, putting operators into namespaces avoids ambiguities.
Garry Lancaster:
> > This doesn't follow: you can't generalise from a single
> > example, especially since I already posted a counter-example.
Martin v. Löwis:
> I believe that this example is flawed. Your example was
>
> #include <iostream>
> namespace ns
> {
> std::ostream& operator<<(std::ostream& os, int i)
> { os << "ns::operator<<\n"; return os; }
>
> void call_op()
> { std::cout << 1; }
> }
>
> int main()
> { ns::call_op(); }
>
> and you were arguing that this produces ambiguities because of
> ADL. This is not the case: the conflict is between ns::operator<< and
> std::ostream::operator<<(int), i.e. between a method and a global
> operator. Even without ADL, members of the operands would be
> considered in overload resolution so this gives a conflict even
> without ADL.
>
> So, this is not a counterexample.
Yup, I was forgetting the op<< for ints was a member
function. As I suspect you already realise, this is not
difficult to remedy: for example, try it with the op<< for
std::basic_string<>, which is non-member and relies
on ADL.
These are all corner cases anyway. Usually ambiguity
is not a problem for operators, whether they be found
by ADL or normal unqualified lookup. If we follow Scott
Meyers' good advice to "do as the ints do" as far as
operator semantics are concerned, there is very little
justification for two different behaviours for the same
operator passed identical parameters. By this measure,
neither example nor counter-example represent good
practice.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net