// **** main problem
namespace A
{ class foo {};
template <typename T> void helper (T) {}
}
namespace B
{ template <typename T> void helper(T ) {}
template <typename T>
class set
{
public:
set()
{ T tmp;
helper(tmp);
}
};
}
int main() { B::set<A::foo *> s; }
// ****
I actually had this happen to me in production code. I ended up
changing
int main() { B::set<A::foo *> s; }
to
int main() { B::set<void *> s; }
to get around Koenig lookup, and static_cast'ing on each use. I really
disliked how I had to abandon type safety. I was in a no win
situation. namespace B was the compiler's STL, and namespace A was a
company-wide header which was not going to change.
I post this to make sure I understand all of the problems Koenig
lookup is supposed to solve. Here are the two big examples of which I
know.
// **** ex 1
#include <iostream>
int main() { std::cout << "foo\n"; }
// **** ex 2
namespace A { class foo {}; void bar(foo ) {} }
int main() { A::foo x; bar(x); }
// ****
Solution #1 - As it stands now without Koenig lookup.
Without Koenig lookup, the first problem of finding the function
operator << can be solved with the ever dreaded "using namespace std;"
or a more limited "using std::operator <<;". The second example can be
made to compile without Koenig lookup with "using namespace A;",
"using A::bar;", or explicitly specifying which bar "int main()
{ A::foo x; A::bar(x); }".
Solution #2
One alternative already proposed in a thread on this newsgroup is to
make each operator function into a member function or into a global
scope global function. (I don't know how lookup would work without
Koenig lookup for infix operator notation, so if necessary change the
lookup rules to consider a member function of the left operand when
using infix operator notation.) This is polluting the global
namespace, but not in a meaningful way. Moving these operators to
global scope does not change the space of functions which the user can
declare and use. With Koenig lookup and the original function in
namespace scope, defining and using another produces an ambiguous call
error. With the original function at global scope, a second ser-
defined function will run afoul of the One Definition Rule. (Arguably
though, solution #2 can be a link time ODR violation, which is not
required to be diagnosed IIRC, whereas as it stands now it's a compile
time error.)
Now, if it was just this simple, I would be for removing Koenig lookup
entirely. However, Koenig lookup may allow, and/or may have been
intended to allow, supplying your own function for another library to
use. You define some function at namespace A for your type in
namespace A, and then you call another library algorithm in namespace
B expecting it to use your function with your type. Was this intended?
Is this desirable? Are there alternatives?
I see how this is desirable, for example std::swap. Specifically, if
you specialize a template, it should (?) be visible in that
translation unit everywhere where that template is visible. I see no
problems with this. Koenig lookup is not involved. However, if I have
a hidden helper function in namespace A, and namespace std has a
hidden helper function with the same name, it shouldn't pick up mine
(a.k.a. the "main problem" of this post).
With partial template function specialization, I think we're good to
go.
Admittingly, it might be too late to get rid of Koenig lookup, as code
has been written which relies on it, so I have two cases:
1- In an ideal world where we're starting from scratch, did I miss any
reasons as to why we should have Koenig lookup? It causes a lot of
real headaches for arguably nothing (with solution #2) or for skipping
some using declarations (With soution #1), so I'm all for getting rid
of it.
2- In this world, how painful would it be to get rid of it? How much
real world code would it break and how hard would it be to fix it? I
see it as possible that someone did some really weird stuff with
Koenig lookup where they defined a function in namespace A and expect
it to be used over in namespace B because of it, and there isn't a
good way to fix that.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Why not use
struct foo_wrapper {
A::foo* ptr;
};
in place of 'A::foo*'.
> 2- In this world, how painful would it be to get rid of it? How much
> real world code would it break and how hard would it be to fix it? I
> see it as possible that someone did some really weird stuff with
> Koenig lookup where they defined a function in namespace A and expect
> it to be used over in namespace B because of it, and there isn't a
> good way to fix that.
One example would be boost::intrusive_ptr.
You could fix this problem by adding the following declaration in
namespace A:
template <typename T> void helper (T* t) { helper<T*>(t); }
The line above won't change namespace A significantly and this might
overcome the "namespace A is not going to change" rationale.
Ganesh
I believe your problem is right here, not with the Koening lookup. An
unrestricted template, taking any kind of type, is just asking for
trouble.
Could it not be
template<class T>
void helper(A::someclass<T>) {}
??
But you cannot use that in template code, if you don't know what
namespaces your T is supposed to come from.
>
> Solution #2
> One alternative already proposed in a thread on this newsgroup is to
> make each operator function into a member function or into a global
> scope global function. (I don't know how lookup would work without
> Koenig lookup for infix operator notation, so if necessary change
> the lookup rules to consider a member function of the left operand
> when using infix operator notation.) This is polluting the global
> namespace, but not in a meaningful way. Moving these operators to
> global scope does not change the space of functions which the user
> can declare and use. With Koenig lookup and the original function in
> namespace scope, defining and using another produces an ambiguous
> call error. With the original function at global scope, a second
> ser- defined function will run afoul of the One Definition Rule.
> (Arguably though, solution #2 can be a link time ODR violation,
> which is not required to be diagnosed IIRC, whereas as it stands
> now it's a compile time error.)
Moving all the operators to global scope removes much of the
usefulness of namespaces. The general idea is of course that things
should be hidden, to avoid conflicting names.
Also, making infix operators class members only works for the left
side argument. Sometimes we want the symmetry of allowing the class on
the right side as well:
myclass operator+(const myclass&, int);
myclass operator+(int, const myclass&); // cannot be a member
Bo Persson
An easy workaround is to add the following forwarding function overload
to namespace A (fortunately you can reopen namespaces without touching
the concerned header files), as it will be preferred over the template
instantiations:
namespace A
{
inline void helper(foo* p) { helper<>(p); }
// or { B::helper<>(p) } if preferred
}
> int main() { B::set<A::foo *> s; }
HTH
Falk
This seems like a case specific fix, and doesn't apply to the general
problem. Specifically, namespaces were meant to solve the problem of
name collision. The C way of doing it was to prepend on a unique
prefix to every extern symbol when creating a library. C++ namespaces
are quite similar. In the end, the compiler just prepends on a unique
prefix to all of the symbols in the namespace.
The difference between the C++ way and the C way is that a user can
have a using declaration so that he doesn't have to type as much, so
that it's quicker and easier on the programmer, and so that it's
easier to read and thus maintain. (I suppose it also gets around the
limited length identifier problem, but that's trivial to fix.
Namespaces effectively increase the length of an identifier, so we
could just get rid of namespaces and put the guaranteed length of
identifiers up to 128 or something.)
The other difference is for the namespace implementor. As it stands
now, anyone implementing a namespace generally should qualify each and
every function call, for fear that a user will use another library
with a function which could cause an ambiguous call due to Koenig
lookup. I expect that this is generally restricted to template
functions, but it's possible with regular function if namespace A
knows about and uses namespace B. namespace B could change an
"internal" function name, and this could break namespace A if
namespace A did not explicity qualify every function call. Yes, we all
know about this, but I want to emphasize that I want to understand the
problem, and what Ganesh gave was a quickie fix which will not work in
the general case. The general case fix now is to qualify every
function call, and hope other library writers do the same.
Unfortunately, plenty of STL implentations do not.
On Jul 25, 11:05 am, "Bo Persson" <b...@gmb.dk> wrote:
> Moving all the operators to global scope removes much of the
> usefulness of namespaces. The general idea is of course that things
> should be hidden, to avoid conflicting names.
Again, as I tried to explain, Koenig lookup and namespaces don't buy
us anything with operators with infix notation.
namespace A
{ class T {};
T operator + (T , T );
}
No one, in any other namespace, anywhere, can declare a function with
signature
operator + (T , T);
Specifically, they're allowed to declare it and define it. No ODR
violation. However, they will never be able to use it with infix
syntax.
namespace A
{ class T {};
T operator + (T , T );
}
namespace B
{ A::T operator + (A::T , A::T );
void foo()
{ A::T a, b;
a + b;//Compile time error, ambiguous call
}
}
int main()
{
A::T a, b;
a + b; //Uses A::operator +
operator + ( a , b );
A::operator + ( a , b );
B::operator + ( a , b );
using B::operator +;
a + b;//Compile time error, ambiguous call
}
Thus putting these operators in the global namespace doesn't really
hurt. Technically, right now we can define operator+(T,T) again in a
different scope, but to call it we'd have to bypass Koenig lookup by
explicitly specifying which operator, which cannot be done with infix
notation.
Koenig lookup was meant to (among other things) allow us to declare
operators at namespace scope and be able to use them without a using
declaration. We could just put the operators with parameters of a type
declared in a namespace at global scope, and this would not restrict
which functions the user may define and use with infix syntax.
> Now, I don't want to start a flame thread, and I apologize for
> bringing this up. However, I do have a couple things I would like
> clarified. To start, here's one of the well known problems with Koenig
> lookup.
>
> // **** main problem
> namespace A
> { class foo {};
> template <typename T> void helper (T) {}
The really bad version of this problem occurs when this helper takes
T&...
> }
> namespace B
> { template <typename T> void helper(T ) {}
....and this one take T const&
> template <typename T>
> class set
> {
> public:
> set()
> { T tmp;
> helper(tmp);
> }
> };
> }
> int main() { B::set<A::foo *> s; }
> // ****
>
> I actually had this happen to me in production code. I ended up
> changing
> int main() { B::set<A::foo *> s; }
> to
> int main() { B::set<void *> s; }
> to get around Koenig lookup, and static_cast'ing on each use. I really
> disliked how I had to abandon type safety. I was in a no win
> situation. namespace B was the compiler's STL, and namespace A was a
> company-wide header which was not going to change.
Yep, this is the well-known "global name reservation" problem that can
make two independently-developed pieces of code incompatible.
> I post this to make sure I understand all of the problems Koenig
> lookup is supposed to solve. Here are the two big examples of which I
> know.
>
> // **** ex 1
> #include <iostream>
> int main() { std::cout << "foo\n"; }
> // **** ex 2
> namespace A { class foo {}; void bar(foo ) {} }
> int main() { A::foo x; bar(x); }
> // ****
I view those as illustrating exactly the same scenario, but if you want to
distinguish
operators from other names you might use:
namespace Math
{
class rational {};
rational abs( rational ) {}
}
template <class Number>
void generic_algorithm(Number x)
{
Number y = abs(x);
};
which is both realistic and demonstrates that sometimes (e.g. in
templates) you don't know the name of the namespace even if you *were*
willing to use explicit qualification.
> Solution #1 - As it stands now without Koenig lookup.
> Without Koenig lookup, the first problem of finding the function
> operator << can be solved with the ever dreaded "using namespace std;"
> or a more limited "using std::operator <<;".
Yeah, but not in a generic printing function (once again, you don't know
the name of the namespace).
> The second example can be
> made to compile without Koenig lookup with "using namespace A;",
> "using A::bar;", or explicitly specifying which bar "int main()
> { A::foo x; A::bar(x); }".
>
> Solution #2
> One alternative already proposed in a thread on this newsgroup is to
> make each operator function into a member function or into a global
> scope global function. (I don't know how lookup would work without
> Koenig lookup for infix operator notation, so if necessary change the
> lookup rules to consider a member function of the left operand when
> using infix operator notation.) This is polluting the global
> namespace, but not in a meaningful way. Moving these operators to
> global scope does not change the space of functions which the user can
> declare and use. With Koenig lookup and the original function in
> namespace scope, defining and using another produces an ambiguous call
> error. With the original function at global scope, a second ser-
> defined function will run afoul of the One Definition Rule. (Arguably
> though, solution #2 can be a link time ODR violation, which is not
> required to be diagnosed IIRC, whereas as it stands now it's a compile
> time error.)
>
>
> Now, if it was just this simple,
Oh, how I wish...
> I would be for removing Koenig lookup
> entirely. However, Koenig lookup may allow, and/or may have been
> intended to allow, supplying your own function for another library to
> use. You define some function at namespace A for your type in
> namespace A, and then you call another library algorithm in namespace
> B expecting it to use your function with your type. Was this intended?
> Is this desirable? Are there alternatives?
http://www.boostpro.com/writing/n1691.html
It didn't get lots of traction in the committee. Actually I think there
might be some support for combining this approach with C++ module
support if we can ever get the time and/or funding to complete that
project:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2006.pdf
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
I bow to your superior kung-fu. I see the value in this now. I still
have to think what position I'll take. At the very least, as it stands
now namespaces don't buy you what most people think they do.
Specifically, would this be a fair assessment?
In all header files, for all function calls, you should either:
1- namespace qualify the function call. I expect this to be the
general case.
2- purposefully not namespace qualify the function call. I expect this
to happen mostly in templates (of type T), and the function has an
argument of T. You expect a user to provide this function for type T,
and thus you want Koenig lookup to find the user supplied function.
Specifically, I was never taught this in school, and absolutely none
of my colleagues know about this either. This is a huge gotcha which
is not known at all. I don't expect it to be as big of a deal when
writing .cpp files, as if you hit some Koenig lookup problem, you have
control and can scope it or not. However, anyone writing interface
header files must make a determination for each function call to
namespace qualify or not. Moreover, this seems quite error prone, as
during the writing of the interface file, it may appear to work fine,
but it could break in the field in a small subset of cases.
On the other hand, as a user of the header file, if you encounter such
a problem, it might be easy enough to just go in and fix it... It's
just a header file. If the function call was not namespace qualified
and it should have been, as a user you could just modify the header
file to explicitly qualify the function call, and it should work.
Right?
However, it would be ... "inconvenient" ... to ask my entire company
to "fix" a header file in the compiler's STL, though this is more a
problem of company policy and tradition than any real coding problem.
--
> At the very least, as it stands now namespaces don't buy you what most
> people think they do.
Yes; I've said the same thing often. Unfortunately in the committee
casual pronouncements about what "most people" want/need/expect without
any backing data are usually viewed at best with mild amusement. So I
try to appeal to stronger arguments if I can find them.
> Specifically, would this be a fair assessment?
> In all header files, for all function calls,
Source files, too.
> you should either:
> 1- namespace qualify the function call. I expect this to be the
> general case.
You can also parenthesize the function name, but that's really horrible.
> 2- purposefully not namespace qualify the function call. I expect this
> to happen mostly in templates (of type T), and the function has an
> argument of T. You expect a user to provide this function for type T,
> and thus you want Koenig lookup to find the user supplied function.
You forgot about the other side of the bargain. When writing code that
uses ADL, you need to document exactly which functions you'll be calling
without qualification and on what arguments.
> Specifically, I was never taught this in school, and absolutely none
> of my colleagues know about this either. This is a huge gotcha which
> is not known at all.
It's well-known in some circles.
> I don't expect it to be as big of a deal when writing .cpp files, as
> if you hit some Koenig lookup problem, you have control and can scope
> it or not.
You don't have control, because you can't really control what happens
(i.e. what functions are declared) in the header files you're using.
> However, anyone writing interface header files must make a
> determination for each function call to namespace qualify or
> not. Moreover, this seems quite error prone, as during the writing of
> the interface file, it may appear to work fine, but it could break in
> the field in a small subset of cases.
Yes. It's not that ADL breaks often; it doesn't.
The problem is that
a. when it breaks there may be no way to fix it (library incompability)
b. once you understand the problem you will waste countless valuable
brain cycles thinking about how to avoid it.
> On the other hand, as a user of the header file, if you encounter such
> a problem, it might be easy enough to just go in and fix it... It's
> just a header file.
In general, no. The header may come from a third party and you may not
have the liberty to change it, either because it will break ABI
compability with something else in your program, or because they've
given you precompiled binaries to go with the header and no source, or
any number of other reasons.
> If the function call was not namespace qualified and it should have
> been, as a user you could just modify the header file to explicitly
> qualify the function call, and it should work. Right?
>
> However, it would be ... "inconvenient" ... to ask my entire company
> to "fix" a header file in the compiler's STL, though this is more a
> problem of company policy and tradition than any real coding problem.
Once we got http://std.dkuug.dk/jtc1/sc22/wg21/docs/lwg-defects.html#225
straightened out, I don't know of any standard library implementations
that are calling user-definable names other than swap without
qualification (and unqualified swap calls are now officially
sanctioned). Unfortunately MSVC's standard library still fully
qualifies swap calls.
See also
http://std.dkuug.dk/jtc1/sc22/wg21/docs/lwg-active.html#226
http://std.dkuug.dk/jtc1/sc22/wg21/docs/lwg-defects.html#227
http://std.dkuug.dk/jtc1/sc22/wg21/docs/lwg-defects.html#229
and
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2001/n1296.asc
for more on this.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
I'm sorry, this might be a little out of scope. Now, I fully agree
that it might not be practical to change a header file from a third
party library, but let me engage in this thought experiment. When they
compiled their library with their "broken" header, the function was
not qualified. At the time, the compiler used ADL to pick the intended
function. Now, if the library writer were to go back and change the
header file by only qualifying that function call and recompile, that
would not change the produced binary at all, would it? Qualifying a
function call to the function that it would normally resolve to should
not change the compiled output at all.
Now, as a user of this library, if I were to just go into the header,
and qualify the function call, just as the hypothetical library writer
did, it should be fine. Things should still proceed upon their merry
way.
I guess if it was inside of a template, it might change the template
file thing that some linkers use to instantiate templates at link
time. I'm not familiar enough with that, but I don't believe any
libraries ship with such a file, so I don't expect this to be a
problem.
--
The D programming language has an alternative method called overload
sets. The idea is that each module represents an "island" of overloaded
functions with the same name. You can import many modules with overloads
of the same name, and each of those also form islands.
Now, when you call a function with that name, if it has a match with an
overload in more than one island, it is an error. But if it has a match
with an overload in one and only one island, it works.
In this way, module writers can create functions without worrying about
name collisions with other, unrelated modules, and yet users won't need
to defensively apply full qualification to the names. Only in the case
of an actual ambiguity, which the compiler will tell you about, will
qualification be necessary.
Here's a fuller explanation with examples:
http://www.digitalmars.com/d/2.0/hijack.html
It's been in use for about a year now, and seems to work as expected.
The other reason for ADL is if the left side of an operator overload is
a basic type. D deals with this by looking at the right side, too, and
looking for "reverse" operator overloads in the name space of the type
of the right operand.
http://www.digitalmars.com/d/2.0/operatoroverloading.html
Both of these seem to work well enough that there hasn't been a call for
adding ADL (cross fingers!).
----
Walter Bright
http://www.digitalmars.com
C, C++, D programming language compilers
> Joshua...@gmail.com wrote:
>> Admittingly, it might be too late to get rid of Koenig lookup, as code
>> has been written which relies on it, so I have two cases:
>> 1- In an ideal world where we're starting from scratch, did I miss any
>> reasons as to why we should have Koenig lookup? It causes a lot of
>> real headaches for arguably nothing (with solution #2) or for skipping
>> some using declarations (With soution #1), so I'm all for getting rid
>> of it.
>
> The D programming language has an alternative method called overload
> sets. The idea is that each module represents an "island" of overloaded
> functions with the same name. You can import many modules with overloads
> of the same name, and each of those also form islands.
>
> Now, when you call a function with that name, if it has a match with an
> overload in more than one island, it is an error. But if it has a match
> with an overload in one and only one island, it works.
>
> In this way, module writers can create functions without worrying about
> name collisions with other, unrelated modules, and yet users won't need
> to defensively apply full qualification to the names. Only in the case
> of an actual ambiguity, which the compiler will tell you about, will
> qualification be necessary.
>
> Here's a fuller explanation with examples:
> http://www.digitalmars.com/d/2.0/hijack.html
>
> It's been in use for about a year now, and seems to work as expected.
The problem is that, unless something has changed, it doesn't solve an
important problem that ADL solves (i.e. "how do you write a generic
library that allows type authors to provide customized operations like
swap?")
> The other reason for ADL is if the left side of an operator overload is
> a basic type.
Huh? fundamental types in C++ don't have an associated namespace. ADL
can't help there.
> D deals with this by looking at the right side, too, and
> looking for "reverse" operator overloads in the name space of the type
> of the right operand.
>
> http://www.digitalmars.com/d/2.0/operatoroverloading.html
>
> Both of these seem to work well enough that there hasn't been a call for
> adding ADL (cross fingers!).
I wonder if anyone's actually doing generic programming in D, then (and
by that, I don't mean just "writing template code.")
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
> On Jul 28, 1:31 am, David Abrahams <d...@boostpro.com> wrote:
>> on Sun Jul 27 2008, JoshuaMaurice-AT-gmail.com wrote:
>> > On the other hand, as a user of the header file, if you encounter such
>> > a problem, it might be easy enough to just go in and fix it... It's
>> > just a header file.
>>
>> In general, no. The header may come from a third party and you may not
>> have the liberty to change it, either because it will break ABI
>> compability with something else in your program, or because they've
>> given you precompiled binaries to go with the header and no source, or
>> any number of other reasons.
>
> I'm sorry, this might be a little out of scope. Now, I fully agree
> that it might not be practical to change a header file from a third
> party library, but let me engage in this thought experiment. When they
> compiled their library with their "broken" header, the function was
> not qualified. At the time, the compiler used ADL to pick the intended
> function. Now, if the library writer were to go back and change the
> header file by only qualifying that function call and recompile, that
> would not change the produced binary at all, would it? Qualifying a
> function call to the function that it would normally resolve to should
> not change the compiled output at all.
No, but the binary may contain a copy of an instantiated function
template. This is known as an ODR violation. If you instantiate that
template again in your own translation unit, the linker may eliminate
either one of them.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
To do so there would be need for a module to reopen a namespace
introduced by a different module, a feature currently not present in D
but that we're mulling over.
As far as I understand the current state of affairs with swap in C++
(which will stay in C++0x) is that people defining using a custom swap
should write:
{
...
Widget w1, w2;
using std::swap;
swap(w1, w2);
...
}
Is this correct?
By and large, I find template functions providing a fallback default
that is supposed to be intercepted not as useful as they may seem. For
starters, one problem with swap is that it is almost guaranteed to do
the wrong thing for a host of types. I'd rather have swap fail to
compile/be visible for such types (via the use of a concept) instead of
having it bite types that it can't chew.
>> The other reason for ADL is if the left side of an operator overload is
>> a basic type.
>
> Huh? fundamental types in C++ don't have an associated namespace. ADL
> can't help there.
>
>> D deals with this by looking at the right side, too, and
>> looking for "reverse" operator overloads in the name space of the type
>> of the right operand.
>>
>> http://www.digitalmars.com/d/2.0/operatoroverloading.html
>>
>> Both of these seem to work well enough that there hasn't been a call for
>> adding ADL (cross fingers!).
>
> I wonder if anyone's actually doing generic programming in D, then (and
> by that, I don't mean just "writing template code.")
We'd need to define generic programming first. This is a serious
question because right now generic programming is many things to many
people. Last time the subject came up, you, Alexander Stepanov, and
yours truly did not agree on the same definition. I don't lay claim I'm
doing it, so I don't have a vested interest, but I'd be hard pressed to
answer if asked for a good definition of generic programming (and
consequently curious in finding one).
Andrei
--
I believe it solves just that problem. If module A has the generic swap,
and module B wishes to create a customized swap, the author of B can
(using an alias declaration) specifically overload B's swap with A's
swap. Overloading across modules is just not the default, it must be
asked for specifically.
>> The other reason for ADL is if the left side of an operator overload is
>> a basic type.
> Huh? fundamental types in C++ don't have an associated namespace. ADL
> can't help there.
The idea is if I need to overload operator + such that:
1 + T
will look up the correct overload of + using the same mechanism that
allows (T+1) to work.
>> D deals with this by looking at the right side, too, and
>> looking for "reverse" operator overloads in the name space of the type
>> of the right operand.
>>
>> http://www.digitalmars.com/d/2.0/operatoroverloading.html
>>
>> Both of these seem to work well enough that there hasn't been a call for
>> adding ADL (cross fingers!).
>
> I wonder if anyone's actually doing generic programming in D, then (and
> by that, I don't mean just "writing template code.")
Yes, there are. I'm happy to answer any more questions about how it works.
----
Walter Bright
http://www.digitalmars.com
C, C++, D programming language compilers
--
I was referring to this example:
//start example
//compiler header
namespace standard
{ template <typename T> void helper(T );
template <typename T> class container
{ public: container() { T inst; helper(inst); } }; //Line in
question,
}
//Third party library header
namespace A
{ class foo {};
template <typename T> void helper(T );
}
//cpp file
int main() { standard::container<A::foo> inst; }
//end example
This will fail. This is the first example I posted. Now, what I
propose doing is qualifying the call in the standard header, from
{ public: container() { T inst; helper(inst); } }; //Line in
question,
to
{ public: container() { T inst; standard::helper(inst); } }; //
Line in question,
For the line in question, the intent was to call standard::helper, not
for ADL to find some other function. Because of ADL, when I use the
standard header with the third party library header, instantiating the
standard template container with the type from the third party library
header, I get an ambiguous function call. The intent of the standard
header was to call standard::helper. I'm just suggesting that it would
be possible to fix this oversight by qualifying it myself. I'm not
talking about a way to define a function, or specialize a template, in
a namespace not under my control.
For cases like this one, I believe that it would not change binary
compatibility at all. Without the third party header file, it will
find standard::helper, instantiate it, and it will call the mangled
standard::helper function. If I were to go in after the library has
been compiled and qualify the call, it won't change the end result:
the compiler pick the same function as before, it will call the same
name mangled standard::helper function.
And again, I'm not sure I'd actually suggest doing this in practice,
but it might be the best workaround for using a poorly written
library.
--
I'd like to add that D's generic programming abilities are based on what
we hope is a thorough understanding of C++ TMP's strengths, weaknesses,
what is important and what isn't. Of course it's possible we've missed
something important, as it hasn't undergone years of thorough thrashing
like C++'s TMP has. But it's far more than a paper airplane, there's a
real implementation that can be used. Review and comments are certainly
welcome.
>> The problem is that, unless something has changed, it doesn't solve an
>> important problem that ADL solves (i.e. "how do you write a generic
>> library that allows type authors to provide customized operations like
>> swap?")
>
> I believe it solves just that problem. If module A has the generic swap,
> and module B wishes to create a customized swap, the author of B can
> (using an alias declaration) specifically overload B's swap with A's
> swap. Overloading across modules is just not the default, it must be
> asked for specifically.
Then something has changed since
http://groups.google.com/group/comp.lang.c++.moderated/msg/a732c53b520f490e
?
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
>> I wonder if anyone's actually doing generic programming in D, then (and
>> by that, I don't mean just "writing template code.")
>
> Yes, there are. I'm happy to answer any more questions about how it works.
I'm interested in seeing examples of people lifting algorithms to their
highest level of generality without loss of efficiency.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
> As far as I understand the current state of affairs with swap in C++
> (which will stay in C++0x) is that people defining using a custom swap
> should write:
>
> {
> ...
> Widget w1, w2;
> using std::swap;
> swap(w1, w2);
> ...
> }
>
> Is this correct?
Unless concept support changes something, I think so. It's ugly, yes.
You can make it nicer by wrapping that swap in a small utility function,
for what that's worth.
> By and large, I find template functions providing a fallback default
> that is supposed to be intercepted not as useful as they may seem.
When the definition depends on nothing more than operations that are
part of the definition
> For starters, one problem with swap is that it is almost guaranteed to
> do the wrong thing for a host of types.
Depends how you define "wrong thing," I suppose. Do you think the
syntax of move construction and move assignment should be allowed to
have arbitrary semantics?
> I'd rather have swap fail to compile/be visible for such types (via
> the use of a concept) instead of having it bite types that it can't
> chew.
>
>> I wonder if anyone's actually doing generic programming in D, then (and
>> by that, I don't mean just "writing template code.")
>
> We'd need to define generic programming first. This is a serious
> question because right now generic programming is many things to many
> people. Last time the subject came up, you, Alexander Stepanov, and
> yours truly did not agree on the same definition.
I don't remember that; can you remind me of the details. Like it or
not, Alex gets to define the term, and I don't think I'd presume to
disagree with his definition.
> I don't lay claim I'm doing it, so I don't have a vested interest, but
> I'd be hard pressed to answer if asked for a good definition of
> generic programming (and consequently curious in finding one).
I recently did a lot of research into what Alex has been up to. If you
go back and read through the things at http://stepanovpapers.com, it
turns out he's had a remarkably consistent and single-minded vision for
what he's been trying to do since the late '70s. It's pretty easy to
find quotations in his work (check the Dr. Dobb's and Italian interviews
especially) that appear to define what GP is.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
> Walter Bright wrote:
>> David Abrahams wrote:
>>> I wonder if anyone's actually doing generic programming in D, then (and
>>> by that, I don't mean just "writing template code.")
>> Yes, there are. I'm happy to answer any more questions about how it works.
>
> I'd like to add that D's generic programming abilities are based on what
> we hope is a thorough understanding of C++ TMP's strengths, weaknesses,
> what is important and what isn't.
But C++ TMP is pretty much irrelevant to Generic Programming.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
I agree you can do GP without TMP, after all Java does it, but without
TMP I suspect GP is not that useful.
--
I suppose a good start would be to take a look at
http://www.digitalmars.com/d/2.0/phobos/std_algorithm.html
Compile an example, and we can have a look at the generated assembler.
--
If I understand what you're driving at, yes, if type T is defined in
module A, and customized swap for T is defined in module B, and generic
swap is in module C, then the user will have to import module B in order
to use the customized swap. Module B should also import C and
specifically merge its swap with C's generic swap.
--
> David Abrahams wrote:
>> I'm interested in seeing examples of people lifting algorithms to their
>> highest level of generality without loss of efficiency.
>
> I suppose a good start would be to take a look at
> http://www.digitalmars.com/d/2.0/phobos/std_algorithm.html
>
> Compile an example, and we can have a look at the generated assembler.
Cool; where can I look at the implementations of these functions? I'd
especially be interested in an implementation of something like
std::unique or std::remove, because I don't see how to do it without D's
reference semantics interfering very seriously with genericity.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
The question is, if generic sort is in module D, and generic sort uses
swap, will sort be able to use the customizations of swap that have been
defined for arbitrary types unknown to the author of sort?
[Note: I don't much care about the "generic" swap -- imagine there
isn't one.]
It sounds like this "merge its swap with C's generic swap" feature is a
new thing since the post cited above, and it might be enough to solve
the problem. It sounds a lot like what I proposed in my "explicit
namespaces" paper, actually.
The downside, if there is one, of a scheme like this is that it's hard
to represent some generic operations (like swap for example) whose
meaning shouldn't be tied in any way to a particular module. This is a
subtle point, which I overlooked for a long time until Peter Dimov
explained it to me in this thread:
http://news.gmane.org/find-root.php?message_id=%3c00d701c5227a%24b3449d20%246501a8c0%40pdimov2%3e
and then argued with me until I understood it. I recommend reading his
posts there.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
The implementation comes in the file dmd/phobos/std/algorithm.d, which
comes in the download available from
http://www.digitalmars.com/d/download.html
Complete library source comes with the download.
--