digitalmars.D.learn - How to specify a template that uses unqualified type, like any normal
- Dominikus Dittes Scherkl (40/40) Aug 14 2017 if I use fixed-type functions, I can do the following:
- Mengu (34/75) Aug 14 2017 hi dominikus
- Steven Schveighoffer (17/68) Aug 14 2017 This isn't exactly supported. Implicit Function Template Instantiation
- Dominikus Dittes Scherkl (10/32) Aug 14 2017 A little unfortunate, because I would consider this the standard
- Dominikus Dittes Scherkl (20/35) Aug 14 2017 Yeah, works fine. I've improved this to
- ag0aep6g (14/31) Aug 14 2017 I'm probably missing something, but copying the parameter to a local
- Jonathan M Davis via Digitalmars-d-learn (14/38) Aug 14 2017 As I understand it, sufficiently smart C++ compilers will figure out tha...
- Steven Schveighoffer (28/33) Aug 15 2017 This isn't that problem.
- Dominikus Dittes Scherkl (13/31) Aug 16 2017 In fact, that was the first thing I tried, but it doesn't exist.
if I use fixed-type functions, I can do the following: uint foo(uint n) { ++n; // modify n - as this function has received a copy of n, this is always possible return 42; } uint bar(const uint n) { assert(foo(n)==42); return 17; } void main() { bar(3); } But if I try the same with a template parameter, it doesn't work: import std.traits; // Unqual uint foo(T)(Unqual!T n) // first try { ++n; // modify should be possible return 42; } uint foo2(T)(T n) // second try { ++n; // modify fails, as T is const return 42; } uint bar(T)(const T n) { assert(foo(n)==42u); // cannot deduce arguments - why?!? assert(foo2(n)==42u); // here it can deduce the arguments, but the function cannot modify n return 17; } void main() { bar(3); } Any ideas what I need to do to make this work?
Aug 14 2017
On Monday, 14 August 2017 at 13:48:36 UTC, Dominikus Dittes Scherkl wrote:if I use fixed-type functions, I can do the following: uint foo(uint n) { ++n; // modify n - as this function has received a copy of n, this is always possible return 42; } uint bar(const uint n) { assert(foo(n)==42); return 17; } void main() { bar(3); } But if I try the same with a template parameter, it doesn't work: import std.traits; // Unqual uint foo(T)(Unqual!T n) // first try { ++n; // modify should be possible return 42; } uint foo2(T)(T n) // second try { ++n; // modify fails, as T is const return 42; } uint bar(T)(const T n) { assert(foo(n)==42u); // cannot deduce arguments - why?!? assert(foo2(n)==42u); // here it can deduce the arguments, but the function cannot modify n return 17; } void main() { bar(3); } Any ideas what I need to do to make this work?hi dominikus you can call functions as func(arg) when compiler can infer the types for your functions but when it's not you'll get an "cannot deduce arguments" error. when you call bar template function, you won't be able to modify the argument n. ++n will not work and will throw an error at compile time. import std.traits : Unqual; import std.stdio : writeln; uint normalFoo(int n) { ++n; return n; } uint constNormalFoo(const int n) { ++n; // will raise error return n; } uint templateFoo(T)(Unqual!T n) { ++n; return n; } uint constTemplateFoo(T)(const Unqual!T n) { ++n; // will raise error return n; } void main() { writeln(normalFoo(42)); // writeln(constNormalFoo(42)); writeln(templateFoo!int(42)); // writeln(constTemplateFoo!int(42)); } more info is available at:
Aug 14 2017
On 8/14/17 9:48 AM, Dominikus Dittes Scherkl wrote:if I use fixed-type functions, I can do the following: uint foo(uint n) { ++n; // modify n - as this function has received a copy of n, this is always possible return 42; } uint bar(const uint n) { assert(foo(n)==42); return 17; } void main() { bar(3); } But if I try the same with a template parameter, it doesn't work: import std.traits; // Unqual uint foo(T)(Unqual!T n) // first try { ++n; // modify should be possible return 42; } uint foo2(T)(T n) // second try { ++n; // modify fails, as T is const return 42; } uint bar(T)(const T n) { assert(foo(n)==42u); // cannot deduce arguments - why?!? assert(foo2(n)==42u); // here it can deduce the arguments, but the function cannot modify n return 17; } void main() { bar(3); } Any ideas what I need to do to make this work?This isn't exactly supported. Implicit Function Template Instantiation (IFTI) will deduce the parameters to be the types that you pass in. You can't deduce them and then change the parameter types. This is a limitation of IFTI that I have struggled with in the past. What you can do, is: auto foo(T)(T n) if (is(T == Unqual!T)) { // normal implementation } auto foo(T)(T n) if (!is(T == Unqual!T) && isImplicitlyConvertible!(T, Unqual!T)) { return foo!(Unqual!T)(n); } Hopefully the inliner cuts out the extra call. -Steve
Aug 14 2017
On Monday, 14 August 2017 at 15:20:28 UTC, Steven Schveighoffer wrote:On 8/14/17 9:48 AM, Dominikus Dittes Scherkl wrote:A little unfortunate, because I would consider this the standard usecase. You only overload functions if they do something special with const or shared or immutable parameters, but for templates you get a different implementation for these all the time, and you didn't even have a chance to avoid that useless code-bloat? What a pitty.uint foo(T)(Unqual!T n) // first try { ++n; // modify should be possible return 42; } Any ideas what I need to do to make this work?This isn't exactly supported. Implicit Function Template Instantiation (IFTI) will deduce the parameters to be the types that you pass in. You can't deduce them and then change the parameter types. This is a limitation of IFTI that I have struggled with in the past.What you can do, is: auto foo(T)(T n) if (is(T == Unqual!T)) { // normal implementation } auto foo(T)(T n) if (!is(T == Unqual!T) && isImplicitlyConvertible!(T, Unqual!T)) { return foo!(Unqual!T)(n); }Ok, I'll try that out.
Aug 14 2017
On Monday, 14 August 2017 at 17:43:44 UTC, Dominikus Dittes Scherkl wrote:On Monday, 14 August 2017 at 15:20:28 UTC, Steven Schveighoffer wrote:Yeah, works fine. I've improved this to T foo(T)(T n) { static if(!is(Unqual!T == T)) return foo!(Unqual!T)(n); else { // normal implementation } } So it's basically 2 lines of overhead. That's acceptable. The check for isImplicitlyConvertible is not necessary, because if it's not it will error out anyway. As this in fact leads to some decrease in code size (as the instances for const or shared parameters are now much smaller or in fact completely removed by the inliner, much less template code duplication happens), I will add this to a lot of templates - seems this will become some sort of standard D boilerplate code. Thanks for the help!What you can do, is: auto foo(T)(T n) if (is(T == Unqual!T)) { // normal implementation } auto foo(T)(T n) if (!is(T == Unqual!T) && isImplicitlyConvertible!(T, Unqual!T)) { return foo!(Unqual!T)(n); }Ok, I'll try that out.
Aug 14 2017
On 08/15/2017 12:14 AM, Dominikus Dittes Scherkl wrote:T foo(T)(T n) { static if(!is(Unqual!T == T)) return foo!(Unqual!T)(n); else { // normal implementation } } So it's basically 2 lines of overhead. That's acceptable. The check for isImplicitlyConvertible is not necessary, because if it's not it will error out anyway.I'm probably missing something, but copying the parameter to a local variable seems simpler than doing it with overloads: ---- T foo(T)(T n) { Unqual!T m = n; ++m; return m; } ----As this in fact leads to some decrease in code size (as the instances for const or shared parameters are now much smaller or in fact completely removed by the inliner, much less template code duplication happens), I will add this to a lot of templates - seems this will become some sort of standard D boilerplate code.Regarding shared, be aware that dmd lets you copy a value type from shared to unshared, but it doesn't emit an atomic load for the copy. So it's generally not thread-safe to do it.
Aug 14 2017
On Monday, August 14, 2017 17:43:44 Dominikus Dittes Scherkl via Digitalmars-d-learn wrote:On Monday, 14 August 2017 at 15:20:28 UTC, Steven Schveighoffer wrote:As I understand it, sufficiently smart C++ compilers will figure out that the code for two template instantiations can be the same even if the types aren't, and they'll merge the definitions in the generated code, so you only really get one. And that's the typical solution for this particular problem. Unfortunately, dmd doesn't have that yet. I don't know what ldc's backend is able to do on this front, but I suspect that for this to fully work, the frontend has to be doing it, in which case, ldc will have it when dmd has it. The other major, related problem is that normally, all of the templates that get instantiated end up in the final code even if that makes no sense (most notably, stuff like isInputRange). So, there's definitely work to be done towards reducing template bloat in the D compilers. - Jonathan M DavisOn 8/14/17 9:48 AM, Dominikus Dittes Scherkl wrote:A little unfortunate, because I would consider this the standard usecase. You only overload functions if they do something special with const or shared or immutable parameters, but for templates you get a different implementation for these all the time, and you didn't even have a chance to avoid that useless code-bloat? What a pitty.uint foo(T)(Unqual!T n) // first try { ++n; // modify should be possible return 42; } Any ideas what I need to do to make this work?This isn't exactly supported. Implicit Function Template Instantiation (IFTI) will deduce the parameters to be the types that you pass in. You can't deduce them and then change the parameter types. This is a limitation of IFTI that I have struggled with in the past.
Aug 14 2017
On 8/14/17 6:27 PM, Jonathan M Davis via Digitalmars-d-learn wrote:As I understand it, sufficiently smart C++ compilers will figure out that the code for two template instantiations can be the same even if the types aren't, and they'll merge the definitions in the generated code, so you only really get one.This isn't that problem. I can define a function foo(int), which accepts all forms of int, and then *inside the function* I can mutate the parameter. If I define foo as taking a template parameter T, then the implementations that accept just a straight int can modify the local copy of the parameter, but implementations that take a const(int) cannot. This means your function can't be the same for multiple instantiations. But there's no true limitation -- const(int) implicitly casts to int. What IFTI would need is a mechanism to change the parameter types to mutable similar to how you can do this: foo(T)(const(T) t); This now generates one function for int, const(int), immutable(int), and t is const within the function. What we need is something like: foo(T)(mutable(T) t) // fictitious type constructor, doesn't work. Or a more general mechanism to modify IFTI when it is deciding the parameters to use based on the call. In my case, I've run into this when I'm trying to use short or ubyte, and someone uses literals: void foo(short s) { ...} void fooT(T)(T t) { foo(s); } foo(1); // ok fooT(1); // error. It would be nice if there was some way to tell IFTI to infer T as short in this case. -Steve
Aug 15 2017
On Tuesday, 15 August 2017 at 14:24:57 UTC, Steven Schveighoffer wrote:What IFTI would need is a mechanism to change the parameter types to mutable similar to how you can do this: foo(T)(const(T) t); This now generates one function for int, const(int), immutable(int), and t is const within the function. What we need is something like: foo(T)(mutable(T) t) // fictitious type constructor, doesn't work.In fact, that was the first thing I tried, but it doesn't exist. Would be a pretty useful addition anyway, because it would allow (some time in the far future) to move from mutable by default to immutable by default.Or a more general mechanism to modify IFTI when it is deciding the parameters to use based on the call. In my case, I've run into this when I'm trying to use short or ubyte, and someone uses literals: void foo(short s) { ...} void fooT(T)(T t) { foo(s); } foo(1); // ok fooT(1); // error. It would be nice if there was some way to tell IFTI to infer T as short in this case.Yes, that would also be very nice. And would be easy to solve: A literal should always be assumed to be the smallest type that can represent it, not int. May be, if compatibilitpy to old bad C is really still so important, integer propagation can be done later on if neccessary, but don't start out with int. That's just so oldschool and most of the time just annoying and there's no technical reason to do so.
Aug 16 2017