www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to specify a template that uses unqualified type, like any normal

reply Dominikus Dittes Scherkl <dominikus scherkl.de> writes:
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
next sibling parent Mengu <mengukagan gmail.com> writes:
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
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
parent reply Dominikus Dittes Scherkl <dominikus scherkl.de> writes:
On Monday, 14 August 2017 at 15:20:28 UTC, Steven Schveighoffer 
wrote:
 On 8/14/17 9:48 AM, Dominikus Dittes Scherkl wrote:
 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.
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.
 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
next sibling parent reply Dominikus Dittes Scherkl <dominikus scherkl.de> writes:
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:
 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.
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!
Aug 14
parent ag0aep6g <anonymous example.com> writes:
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
prev sibling parent reply Jonathan M Davis via Digitalmars-d-learn writes:
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:
 On 8/14/17 9:48 AM, Dominikus Dittes Scherkl wrote:
 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.
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.
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 Davis
Aug 14
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
parent Dominikus Dittes Scherkl <dominikus scherkl.de> writes:
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