Koenig lookup in D?

monarch_dodra monarchdodra at gmail.com
Tue Aug 27 00:45:13 PDT 2013


Koenig lookup in C++ ( also known as
http://en.wikipedia.org/wiki/Argument-dependent_name_lookup ), is
a mechanism that alows functions (in particular templates) to
call a function that operates on an object of a specific
namespace, without having to qualify the namespace of the
function.

The root reason for this mechanism, is that it allows writing
templated functions that can operate on objects from any
namespace.

For example:

//----
namespace foo
{
      struct A{};
      void do_it(A);
}
namespace bar
{
      struct A{};
      void do_it(A);
}

template <typename T>
void doer(T t)
{
      do_it(t); //[1]
}

int main()
{
      foo::A fa;
      bar::A ba;
      doer(fa); //OK!
      doer(ba); //OK!
}
//----

In this example, the template doer is able to call "do_it"
without qualifying, which allows it to compile. This is *key* in
allowing templates in C++ to operate on function from any
namespace.

This is very important for working with "meta interfaces". If any
body here has used the boost graph library, you'd know you can do
some real cool things with this: BGL defines *algorithms*. You
define the objects and their corresponding functions. Pass your
*object* to the algorithm, and *it* will find your functions.

----------------

In D, this might not seem necessary, since you don't *need* to
qualify function calls, if they uniquely resolve. On the other
hand, with D's anti hijack rules, *all* functions must be know
*at* the call site. What this basically means is that while
Koenig lookup is not "needed" per se, it doesn't work either.

Here is an example, at its simplest: I'd like to define a range,
but for implementation reasons, I'll make front/popFront/empty as
non-members. This is supposed to work, right? After all, that's
what UFCS is for. Let's try it!

//--
module a;

struct A
{}

@property bool empty(A)
{
      return false;
}

@property int front(A)
{
      return 0;
}

void popFront(A)
{}
//--
import a;
void main()
{
      A a;
      bool b = a.empty; //OK
      int  f = a.front; //OK
      a.popFront();     //OK
}
//--

Sweet deal, right? We just created an awesome new range! Did we
though? What happens if we call "assert(isInputRange!R)"? I'll
tell you what happens: An Error!

But why? Simple! std.range.isInputRange is not aware of the
functions `a.front`/`a.popFront`/`a.empty`. As such, the code we
wrote and compiles in main *doesn't* compile in std.range.

What this means is that it turns out our awesome new range,
wasn't really a range :( That's -1 point for extendability.

------------------------

Does this seem normal to you? It doesn't to me. Arguably, we've
never had the need for this, but at the same time, we don't have
any kicks ass BGL-like extend-able library either.

I think that D's non-hijack rules are pretty sweet, but I also
think that D should apply Koenig's rules: If a template is
dependent on a parameter from a particular module (say "a"), then
the functions inside that module should also be taken into
account when building the template instance. It would be
*partial* hijack in the sense that you can only hijack the
templates that operate on *your* types anyways, but in no case
change the behavior of that of others'.

This would, of course, be a MASSIVE change, so it would require
DIP and all that jazz, but I wouldn't mind starting with a little
bit of discussion on the issue, see if anybody has workarounds to
share, etc...


More information about the Digitalmars-d mailing list