Bug 7802 – UFCS functions get lost when type is transmitted to template

Status
NEW
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2012-03-30T23:10:11Z
Last change time
2024-12-13T17:59:20Z
Assigned to
No Owner
Creator
Walter Bright
Moved to GitHub: dmd#18430 →

Comments

Comment #0 by bugzilla — 2012-03-30T23:10:11Z
a.d ------------- void foo(T)(T t) { t.func(); } ------------- test.d ------------- class C { } void func(C c) { } void test(C c) { foo(c); ------------- This fails to compile because a.d doesn't know about test.func(C c), and so does not find it when attempting to resolve t.func() using UFCS. The solution is to: If func() is not found as a member function of t, then look in the template instantiation scope for a func(t). If found, use that as the UFCS function, and then mark the template instantiation as LOCAL to the instantiation context. This will ensure that the mangled name of the instantiation will not conflict with other instantiations of foo(T) with other func(T) functions.
Comment #1 by bugzilla — 2012-03-30T23:12:06Z
Comment #2 by timon.gehr — 2012-03-31T04:22:31Z
What happens if there is an UFCS match both in the instantiation scope and the template declaration scope?
Comment #3 by code — 2012-03-31T10:28:47Z
That smells like a very hacky special case. Picking up symbols from the instantiation scope is very ambivalent. I'd argue that importing test in class C should fix the UFCS behavior, but it currently doesn't because func is found as a member of C. class C { import test; }
Comment #4 by deadalnix — 2012-04-01T23:23:20Z
As mentioned in the newsgroup, templates instantiation and UFCS are 2 orthogonal problems and must be treated as such. The function can be passed as template parameter, the module name to import as a string (and imported via mixin("import " ~ moduleName); ) or whatever. Before trying to solve that problem, we'd better be sure this is a problem.
Comment #5 by schveiguy — 2012-04-04T07:30:43Z
I would suggest instead of using the instantiation scope, use the scope of the defined type. Then the notion of "LOCAL" templates is not needed. For example, if struct S is defined in foo.d, and you instantiate template function func in bar.d: void func(T)(T t) { t.baz(); } and baz is not a member function, look it up from two scopes -- the template definition scope (i.e. bar.d) and the scope where S is defined (i.e. foo.d). This means S's API, including UFCS, is identical no matter where you use it from. Prefer type definition scope over template definition scope, to keep the type's API intact.
Comment #6 by code — 2012-04-04T11:11:46Z
>look it up from two scopes -- the template definition scope (i.e. bar.d) and the scope where S is defined (i.e. foo.d). I thought this too, although the need to extend unknown types at the template scope will be limited. There is one important use-case where this fails though. Extending library types so that they fit a library template, e.g. to use std.stream.Stream with std.algorithm.find I need to modify phobos.
Comment #7 by schveiguy — 2012-04-04T11:33:27Z
(In reply to comment #6) > >look it up from two scopes -- the template definition scope (i.e. bar.d) and the scope where S is defined (i.e. foo.d). > > I thought this too, although the need to extend unknown types > at the template scope will be limited. Templates already introduce a large level of binary incompatibility. This proposal of LOCAL templates will make it even worse, to the point where possibly no binary compatibility will be feasible as long as UFCS is involved. consider that I could import some module that declares a UFCS method foo. Then I call a template function that uses this method. In another module, I also import the same module for foo, call the same template, which generates the exact same code for the exact same function, but now has TWO identical instantiations based on the instantiating module's name. > There is one important use-case where this fails though. > Extending library types so that they fit a library template, e.g. > to use std.stream.Stream with std.algorithm.find I need to modify > phobos. You can do this probably with alias this. But one thing I am concerned about is how a type can arbitrarily take on different API depending on the modules you import. It makes things confusing and unexpected. I'd rather have the type define what API it takes, along with the template, who is obviously free to extend any types it wants. My main concern with UFCS is about the use case where we have an existing method m, which I want to move outside the class or struct (for whatever reason). Both Walter's and my proposals should cover this case, but mine I think does it without adding unnecessary bloat or complexity to the name mangling system. My proposal is also a subset of Walter's. That is, we could *still* go the LOCAL template route later without breaking existing code.
Comment #8 by k.hara.pg — 2012-04-05T01:01:35Z
There is two different situations around template instantiations and UFCS. See following example. --- kernel.d --- struct S { int val; } @property int foo(ref S s) { return s.val; } --- extra.d --- import kernel; @property int bar(ref S s) { return s.val * 2; } --- util.d --- auto testfoo(T)(T t) { return t.foo; } auto testbar(T)(T t) { return t.bar; } --- test.d --- import kernel, extra, util; void main() { auto s = S(1); assert(s.foo == 1); // mod.foo(s), OK assert(s.bar == 2); // extra.bar(s), OK assert(testfoo(s) == 1); // cannot instantiate assert(testbar(s) == 2); // cannot instantiate } Walter's suggestion supports both testfoo and testbar, but I'm afraid that it causes code bloating. Steven's suggestion supports testfoo, but cannot support testbar. I think Steven's way and the case of testfoo is similar to C++ ADL. But in D, a namespace associated with a module is not opened, so unintended name lookup isn't in there. But, the case of testbar requires making relations between kernel and extra modules in test.d module. For now I have no idea about it.
Comment #9 by schveiguy — 2012-04-05T04:53:22Z
(In reply to comment #8) > Walter's suggestion supports both testfoo and testbar, but I'm afraid that it > causes code bloating. > Steven's suggestion supports testfoo, but cannot support testbar. What about a compromise? testfoo will be compiled without the LOCAL moniker, but testbar will compile with it. In other words, implement both ideas. In any case, you will be probably doing the same underlying work for both solutions to determine what scope is necessary to do a symbol lookup.
Comment #10 by k.hara.pg — 2014-08-26T08:10:28Z
*** Issue 8265 has been marked as a duplicate of this issue. ***
Comment #11 by robert.schadek — 2024-12-13T17:59:20Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/18430 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB