www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - A Huge Bummer When Using alias this

reply Jack Stouffer <jack jackstouffer.com> writes:
alias this is only useful when not using any function that relies 
on the template constraints in std.traits.

For example

import std.traits;

void someFunc(N)(N val) if (isNumeric!N)
{
     int b = val;
}

void main()
{
     import std.typecons;
     Nullable!int a = 42;

     someFunc(a);
}

$ dmd test.d
test.d(13): Error: template someFunc cannot deduce function from 
argument types !()(Nullable!(int))

Removing the template constraint makes it compile and run just 
fine. What's a good work around for these types of issues?
Mar 24 2016
next sibling parent reply Jerry <Kickupx gmail.com> writes:
On Thursday, 24 March 2016 at 21:26:00 UTC, Jack Stouffer wrote:
 void someFunc(N)(N val) if (isNumeric!N)
 {
     int b = val;
 }

 void main()
 {
     import std.typecons;
     Nullable!int a = 42;

     someFunc(a);
 }
Basicly you need to test if param is a Nullable and if it is get the template param first param. The FirstTemplateParamMatches template is more psuedo code. Maybe there is a nicer way to get the first template param. void someFunc(N)(N val) if(isInstanceOf!(Nullable, N) && FirstTemplateParamMatches!(isNumeric, N)|| isNumeric!(N)) {} template TemplateParamMatches(alias C, T) { enum TemplateParamMatches = C(mixin("fullyQualifiedName!(T).split(\"!(\").split(\")\")")); } But if you want to use several functions you can do some template overloading. Then one for Nullable which simply forwards the function. void someFunc(N)(Nullable!N val) if(isNumeric!(N)) { someFunc(val.get); }
Mar 24 2016
parent reply Jack Stouffer <jack jackstouffer.com> writes:
On Thursday, 24 March 2016 at 22:31:01 UTC, Jerry wrote:
 Basicly you need to test if param is a Nullable and if it is 
 get the
 template param first param.
 The FirstTemplateParamMatches template is more psuedo code.
 Maybe there is a nicer way to get the first template param.
You just illustrated my point exactly. This doesn't scale, you can't create special rules for every type when you're writing a library. Nullable is aliased to the get function, which returns int in this case. YOU SHOULDN'T HAVE TO DO THIS. This is the exact use case of alias this and it doesn't work.
Mar 24 2016
next sibling parent Jerry <Kickupx gmail.com> writes:
On Thursday, 24 March 2016 at 22:52:20 UTC, Jack Stouffer wrote:
 You just illustrated my point exactly. This doesn't scale, you 
 can't create special rules for every type when you're writing a 
 library. Nullable is aliased to the get function, which returns 
 int in this case. YOU SHOULDN'T HAVE TO DO THIS.

 This is the exact use case of alias this and it doesn't work.
My intuition tells me some sort of ducktyping should be possible here. Check out https://code.dlang.org/packages/quack or else you could define some primitives like isInputRange eg. SupportsMath(T) or whatever which I guess is what quack is doing.
Mar 24 2016
prev sibling parent deadalnix <deadalnix gmail.com> writes:
On Thursday, 24 March 2016 at 22:52:20 UTC, Jack Stouffer wrote:
 You just illustrated my point exactly. This doesn't scale, you 
 can't create special rules for every type when you're writing a 
 library. Nullable is aliased to the get function, which returns 
 int in this case. YOU SHOULDN'T HAVE TO DO THIS.

 This is the exact use case of alias this and it doesn't work.
Thing is, this is because of the constraint you use. Constraints that test capabilities would work, as well as constraints that check coercability. If you want to check for something that convert to an integral, rather than check if it ACTUALLY IS an integral, just use is(T : int) in your case.
Mar 24 2016
prev sibling next sibling parent Meta <jared771 gmail.com> writes:
On Thursday, 24 March 2016 at 21:26:00 UTC, Jack Stouffer wrote:
 alias this is only useful when not using any function that 
 relies on the template constraints in std.traits.

 For example

 import std.traits;

 void someFunc(N)(N val) if (isNumeric!N)
 {
     int b = val;
 }

 void main()
 {
     import std.typecons;
     Nullable!int a = 42;

     someFunc(a);
 }

 $ dmd test.d
 test.d(13): Error: template someFunc cannot deduce function 
 from argument types !()(Nullable!(int))

 Removing the template constraint makes it compile and run just 
 fine. What's a good work around for these types of issues?
It has been brought up quite a few times before and if I recall Andrei wasn't all that concerned. For what reason I'm not sure, as alias this is supposed to introduce a subtype yet it clearly isn't because template constraints will fail when they shouldn't. Basically there's no workaround aside from defining your own isNumeric and forwarding to std.traits.isNumeric.
Mar 24 2016
prev sibling next sibling parent reply ZombineDev <petar.p.kirov gmail.com> writes:
On Thursday, 24 March 2016 at 21:26:00 UTC, Jack Stouffer wrote:
 alias this is only useful when not using any function that 
 relies on the template constraints in std.traits.

 For example

 import std.traits;

 void someFunc(N)(N val) if (isNumeric!N)
 {
     int b = val;
 }

 void main()
 {
     import std.typecons;
     Nullable!int a = 42;

     someFunc(a);
 }

 $ dmd test.d
 test.d(13): Error: template someFunc cannot deduce function 
 from argument types !()(Nullable!(int))

 Removing the template constraint makes it compile and run just 
 fine. What's a good work around for these types of issues?
`isNumeric(T)` explicitly allows only built-in types (int, float, etc.) and prevents user-defined types. Use `if (is(NumericTypeOf!T N))` if you want to allow types `T` that can convert to some numeric type `N`.
Mar 24 2016
parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Mar 24, 2016 at 11:04:32PM +0000, ZombineDev via Digitalmars-d wrote:
 On Thursday, 24 March 2016 at 21:26:00 UTC, Jack Stouffer wrote:
alias this is only useful when not using any function that relies on the
template constraints in std.traits.

For example

import std.traits;

void someFunc(N)(N val) if (isNumeric!N)
{
    int b = val;
}

void main()
{
    import std.typecons;
    Nullable!int a = 42;

    someFunc(a);
}

$ dmd test.d
test.d(13): Error: template someFunc cannot deduce function from argument
types !()(Nullable!(int))

Removing the template constraint makes it compile and run just fine.
What's a good work around for these types of issues?
`isNumeric(T)` explicitly allows only built-in types (int, float, etc.) and prevents user-defined types. Use `if (is(NumericTypeOf!T N))` if you want to allow types `T` that can convert to some numeric type `N`.
[...] IMO, isNumeric is a misnomer. It really should be named isBuiltinNumeric or something along those lines. A "real" isNumeric ought to test if the type supports arithmetic operations... In this particular case, though, I'm not sure why you need to use isNumeric, since you already know you want int to be the target type, so you could just write: void someFunc(N)(N val) if (is(N : int)) ... and it should work. T -- Let X be the set not defined by this sentence...
Mar 24 2016
prev sibling next sibling parent reply Simen Kjaeraas <simen.kjaras gmail.com> writes:
On Thursday, 24 March 2016 at 21:26:00 UTC, Jack Stouffer wrote:
 alias this is only useful when not using any function that 
 relies on the template constraints in std.traits.

 For example

 import std.traits;

 void someFunc(N)(N val) if (isNumeric!N)
 {
     int b = val;
 }

 void main()
 {
     import std.typecons;
     Nullable!int a = 42;

     someFunc(a);
 }

 $ dmd test.d
 test.d(13): Error: template someFunc cannot deduce function 
 from argument types !()(Nullable!(int))

 Removing the template constraint makes it compile and run just 
 fine. What's a good work around for these types of issues?
Simply put, isNumeric doesn't do what you think it does. It checks if the type is *exactly* one of the built-in numeric types (ints and floats of various known-at-compile-time sizes). Even BigInt, a part of Phobos that is explicitly supposed to work as a numeric thing, is not numeric accordion to isNumeric. IMO, isNumeric should be renamed isBuiltInNumeric or something to that effect, as it obviously does not test what it says it does. In addtion, there should be a function somewhat like numericBehavior below: template supportsBinOp(string op, T, U = T) { enum supportsBinOp = __traits(compiles, (T t, U u) { return mixin("t "~op~" u"); }); } template supportsUnOp(string op, T) { enum supportsUnOp = __traits(compiles, (T t) { return mixin(op~" t"); }); } template numericBehavior(T) { enum numericBehavior = supportsBinOp!("+", T) && supportsBinOp!("+", T, int) && supportsBinOp!("-", T) && supportsBinOp!("-", T, int) && supportsBinOp!("*", T) && supportsBinOp!("*", T, int) && supportsBinOp!("*", T) && supportsBinOp!("*", T, int) && supportsBinOp!("+=", T) && // Should opOpAssign be required? supportsBinOp!("+=", T, int) && supportsBinOp!("-=", T) && supportsBinOp!("-=", T, int) && supportsBinOp!("*=", T) && supportsBinOp!("*=", T, int) && supportsBinOp!("*=", T) && supportsBinOp!("*=", T, int) && //supportsUnOp!("++", T) && // Should these //supportsUnOp!("-+", T) && // be included? supportsUnOp!("+", T) && supportsUnOp!("-", T); } unittest { import std.typecons : Nullable; import std.bigint : BigInt; import std.meta : AliasSeq; import std.complex : Complex; alias goodTypes = AliasSeq!( byte, short, int, long, ubyte, ushort, uint, ulong, float, double, real, BigInt, Nullable!int, Complex!float ); foreach (e; goodTypes) { static assert(numericBehavior!e, "Expected "~e.stringof~" to have numeric behavior,"); } alias badTypes = AliasSeq!( string, int*, Object, void, Nullable!string, immutable int // Should this be on the list? ); foreach (e; badTypes) { static assert(!numericBehavior!e, "Did not expect "~e.stringof~" to have numeric behavior,"); } } This tests for behavior, not simply type. I'm not saying isNumeric is a bad template, it just has a confusing name.
Mar 24 2016
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 03/24/2016 08:22 PM, Simen Kjaeraas wrote:
 IMO, isNumeric should be renamed isBuiltInNumeric
Better define isNumericLike. -- Andrei
Mar 24 2016
prev sibling parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Thursday, March 24, 2016 21:26:00 Jack Stouffer via Digitalmars-d wrote:
 alias this is only useful when not using any function that relies
 on the template constraints in std.traits.

 For example

 import std.traits;

 void someFunc(N)(N val) if (isNumeric!N)
 {
      int b = val;
 }

 void main()
 {
      import std.typecons;
      Nullable!int a = 42;

      someFunc(a);
 }

 $ dmd test.d
 test.d(13): Error: template someFunc cannot deduce function from
 argument types !()(Nullable!(int))

 Removing the template constraint makes it compile and run just
 fine. What's a good work around for these types of issues?
In general, using alias this with templates is a recipe for disaster. It's downright trivial to write a template constraint that works thanks to implicit conversion and end up with a function that doesn't compile, because it never explicitly converts the type. As a result, the traits in std.traits specifically do _not_ operate on implicitly convertible types - only on the exact type. It avoids a whole mess of pain. Any templated functions that _want_ to accept implicit conversions, need to be explicitly written with that in mind, and they need to make sure that they explicitly convert the given variable to the target type and not leave it as whatever type it is, because if the conversion is not forced, then it's unlikely to happen, and you'll get compilation errors - or worse, weird, unexpected behaviors that do compile but do the wrong thing. So, if you want to use an implicit conversion, traits like isNumeric are not what you want at all. You want to check that the type converts to exactly what you want it to convert to. In this case, assigning it to an int, so that's what you want to test. e.g. void someFunc(N)(N val) if(is(T : int)) { int b = val; } And note that with your example, using isNumeric!N is completely wrong in the first place, because while int is numeric, not all numeric types - built-in or otherwise - will implicitly convert to int. For instance, with your example someFunc(long.max) will pass the template constraint but fail to compile. So, even if isNumeric worked with implicit conversions, using it for someFunc would still be wrong. But the bottom line is that if you want to use an implicit conversion, then test for that conversion explicitly and then do the conversion explicitly within the function. Don't write a template constraint where implicit conversion to a range of types is allowed - make it one type explicitly - and don't write a template constraint that allows implicit conversions but doesn't actually force the conversion. If you don't follow those rules, then at best, you're going to end up with code that works with some types and not others, and at worst, you could get some nasty, subtle bugs when conversions happen in only part of the function. - Jonathan M Davis
Mar 24 2016