digitalmars.D - A Huge Bummer When Using alias this
- Jack Stouffer (19/19) Mar 24 2016 alias this is only useful when not using any function that relies
- Jerry (25/35) Mar 24 2016 Basicly you need to test if param is a Nullable and if it is get
- Jack Stouffer (6/11) Mar 24 2016 You just illustrated my point exactly. This doesn't scale, you
- Meta (7/26) Mar 24 2016 It has been brought up quite a few times before and if I recall
- ZombineDev (5/24) Mar 24 2016 `isNumeric(T)` explicitly allows only built-in types (int, float,
- H. S. Teoh via Digitalmars-d (13/45) Mar 24 2016 [...]
- Simen Kjaeraas (70/89) Mar 24 2016 Simply put, isNumeric doesn't do what you think it does. It
- Andrei Alexandrescu (2/3) Mar 24 2016 Better define isNumericLike. -- Andrei
- Jonathan M Davis via Digitalmars-d (38/57) Mar 24 2016 In general, using alias this with templates is a recipe for disaster. It...
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
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
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
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
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
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
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
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:[...] 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...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
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
On 03/24/2016 08:22 PM, Simen Kjaeraas wrote:IMO, isNumeric should be renamed isBuiltInNumericBetter define isNumericLike. -- Andrei
Mar 24 2016
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









Jerry <Kickupx gmail.com> 