www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - implicit conversion

reply "uri" <uri gmail.com> writes:
Hi,

I'm trying to allow implicit conversions for my own type 
happening. I have the following:

----
import std.math;
import std.traits;

struct S(T)
if(isFloatingPoint!T)
{
     T val;
     alias val this;
}
void main()
{

     auto s = S!float();
     assert(isNaN(s));
     s = 10.0;
     assert(!isNaN(s));
}
----

But I get a compile time error:

----
Error: template std.math.isNaN cannot deduce function from 
argument types !()(S!float), candidates are:

std/math.d(4171):        std.math.isNaN(X)(X x) if 
(isFloatingPoint!X)
----

Is there a way I can to do this, maybe opCall/opCast (I tried 
these but failed)?

Cheers,
uri
Aug 11 2014
next sibling parent "uri" <uri gmail.com> writes:
Sorry should add this is on 2.066.0-rc2 and it used to work on 
2.064.

Cheers,
uri

On Tuesday, 12 August 2014 at 06:21:19 UTC, uri wrote:
 Hi,

 I'm trying to allow implicit conversions for my own type 
 happening. I have the following:

 ----
 import std.math;
 import std.traits;

 struct S(T)
 if(isFloatingPoint!T)
 {
     T val;
     alias val this;
 }
 void main()
 {

     auto s = S!float();
     assert(isNaN(s));
     s = 10.0;
     assert(!isNaN(s));
 }
 ----

 But I get a compile time error:

 ----
 Error: template std.math.isNaN cannot deduce function from 
 argument types !()(S!float), candidates are:

 std/math.d(4171):        std.math.isNaN(X)(X x) if 
 (isFloatingPoint!X)
 ----

 Is there a way I can to do this, maybe opCall/opCast (I tried 
 these but failed)?

 Cheers,
 uri
Aug 11 2014
prev sibling parent reply Jonathan M Davis via Digitalmars-d-learn writes:
On Tue, 12 Aug 2014 06:21:17 +0000
uri via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> wrote:

 Hi,

 I'm trying to allow implicit conversions for my own type
 happening. I have the following:

 ----
 import std.math;
 import std.traits;

 struct S(T)
 if(isFloatingPoint!T)
 {
      T val;
      alias val this;
 }
 void main()
 {

      auto s = S!float();
      assert(isNaN(s));
      s = 10.0;
      assert(!isNaN(s));
 }
 ----

 But I get a compile time error:

 ----
 Error: template std.math.isNaN cannot deduce function from
 argument types !()(S!float), candidates are:

 std/math.d(4171):        std.math.isNaN(X)(X x) if
 (isFloatingPoint!X)
 ----

 Is there a way I can to do this, maybe opCall/opCast (I tried
 these but failed)?
The problem is that isNaN is now templatized, and its constraint uses isFloatingPoint, which requires that the type _be_ a floating point type, not that it implicitly convert to one. So, as it stands, isNAN cannot work with any type which implicitly converts to a floating point value. Either it will have to be instantiated with the floating point type - e.g. isNaN!float(s) - or you're going to have to explicitly cast s to a floating point type. You can open a bug report - https://issues.dlang.org - and mark it as a regression, and it might get changed, but the reality of the matter is that templates don't tend to play well with implicit conversions. It's _far_ too easy to allow something in due to an implicit conversion and then have it not actually work, because the value is never actually converted. In general, I would strongly advise against attempting to give types implicit conversions precisely because they tend to not play nicely with templates. - Jonathan M Davis
Aug 11 2014
next sibling parent "uri" <email ether.com> writes:
On Tuesday, 12 August 2014 at 06:37:45 UTC, Jonathan M Davis via 
Digitalmars-d-learn wrote:
 On Tue, 12 Aug 2014 06:21:17 +0000
 uri via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> 
 wrote:

 Hi,

 I'm trying to allow implicit conversions for my own type
 happening. I have the following:

 ----
 import std.math;
 import std.traits;

 struct S(T)
 if(isFloatingPoint!T)
 {
      T val;
      alias val this;
 }
 void main()
 {

      auto s = S!float();
      assert(isNaN(s));
      s = 10.0;
      assert(!isNaN(s));
 }
 ----

 But I get a compile time error:

 ----
 Error: template std.math.isNaN cannot deduce function from
 argument types !()(S!float), candidates are:

 std/math.d(4171):        std.math.isNaN(X)(X x) if
 (isFloatingPoint!X)
 ----

 Is there a way I can to do this, maybe opCall/opCast (I tried
 these but failed)?
The problem is that isNaN is now templatized, and its constraint uses isFloatingPoint, which requires that the type _be_ a floating point type, not that it implicitly convert to one. So, as it stands, isNAN cannot work with any type which implicitly converts to a floating point value. Either it will have to be instantiated with the floating point type - e.g. isNaN!float(s) - or you're going to have to explicitly cast s to a floating point type. You can open a bug report - https://issues.dlang.org - and mark it as a regression, and it might get changed, but the reality of the matter is that templates don't tend to play well with implicit conversions. It's _far_ too easy to allow something in due to an implicit conversion and then have it not actually work, because the value is never actually converted. In general, I would strongly advise against attempting to give types implicit conversions precisely because they tend to not play nicely with templates. - Jonathan M Davis
Thanks for the info. I'm happy to change my code and remove the implicit conversion. It was just for a convenience factor (floats with accumulated error). Cheers, uri
Aug 12 2014
prev sibling parent reply "Meta" <jared771 gmail.com> writes:
On Tuesday, 12 August 2014 at 06:37:45 UTC, Jonathan M Davis via 
Digitalmars-d-learn wrote:
 The problem is that isNaN is now templatized, and its 
 constraint uses
 isFloatingPoint, which requires that the type _be_ a floating 
 point type, not
 that it implicitly convert to one. So, as it stands, isNAN 
 cannot work with
 any type which implicitly converts to a floating point value. 
 Either it will
 have to be instantiated with the floating point type - e.g. 
 isNaN!float(s) -
 or you're going to have to explicitly cast s to a floating 
 point type.

 You can open a bug report - https://issues.dlang.org - and mark 
 it as a
 regression, and it might get changed, but the reality of the 
 matter is that
 templates don't tend to play well with implicit conversions. 
 It's _far_ too
 easy to allow something in due to an implicit conversion and 
 then have it not
 actually work, because the value is never actually converted. 
 In general, I
 would strongly advise against attempting to give types implicit 
 conversions
 precisely because they tend to not play nicely with templates.

 - Jonathan M Davis
I think this should be considered a bug. A type with alias this should work in all cases that the aliased type would. If the function fails to be instantiated with S!float, then it should be forwarded to the S's val member.
Aug 12 2014
parent reply Jonathan M Davis via Digitalmars-d-learn writes:
On Tue, 12 Aug 2014 13:17:37 +0000
Meta via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> wrote:

 On Tuesday, 12 August 2014 at 06:37:45 UTC, Jonathan M Davis via
 Digitalmars-d-learn wrote:
 The problem is that isNaN is now templatized, and its
 constraint uses
 isFloatingPoint, which requires that the type _be_ a floating
 point type, not
 that it implicitly convert to one. So, as it stands, isNAN
 cannot work with
 any type which implicitly converts to a floating point value.
 Either it will
 have to be instantiated with the floating point type - e.g.
 isNaN!float(s) -
 or you're going to have to explicitly cast s to a floating
 point type.

 You can open a bug report - https://issues.dlang.org - and mark
 it as a
 regression, and it might get changed, but the reality of the
 matter is that
 templates don't tend to play well with implicit conversions.
 It's _far_ too
 easy to allow something in due to an implicit conversion and
 then have it not
 actually work, because the value is never actually converted.
 In general, I
 would strongly advise against attempting to give types implicit
 conversions
 precisely because they tend to not play nicely with templates.

 - Jonathan M Davis
I think this should be considered a bug. A type with alias this should work in all cases that the aliased type would. If the function fails to be instantiated with S!float, then it should be forwarded to the S's val member.
AFAIK, the only time that the implicit conversion would take place is when the type is being used in a situation where it doesn't work directly but where the aliased type is used. In that case, the compiler sees the accepted types and sees that the type can implicitly convert to one of the accepted types and thus does the conversion. So, it knows that the conversion will work before it even does it. The compiler never attempts to do the conversion just to see whether it will work, which is essentially what it would have to do when attempting to use the type with a templated function. You can certainly create an enhancement request for such behavior, but I have no idea how likely it is get implemented. There are currently _no_ cases where the compiler does anything with template instantiations to try and make them pass if simply trying to instantiate them with the given type failed. - Jonathan M Davis
Aug 12 2014
parent reply "Meta" <jared771 gmail.com> writes:
On Tuesday, 12 August 2014 at 14:26:46 UTC, Jonathan M Davis via
Digitalmars-d-learn wrote:
 AFAIK, the only time that the implicit conversion would take 
 place is when the
 type is being used in a situation where it doesn't work 
 directly but where the
 aliased type is used. In that case, the compiler sees the 
 accepted types and
 sees that the type can implicitly convert to one of the 
 accepted types and
 thus does the conversion. So, it knows that the conversion will 
 work before it
 even does it. The compiler never attempts to do the conversion 
 just to see
 whether it will work, which is essentially what it would have 
 to do when
 attempting to use the type with a templated function. You can 
 certainly create
 an enhancement request for such behavior, but I have no idea 
 how likely it is
 get implemented. There are currently _no_ cases where the 
 compiler does
 anything with template instantiations to try and make them pass 
 if simply
 trying to instantiate them with the given type failed.

 - Jonathan M Davis
What I mean is that this breaks the Liskov Substitution Principle, which alias this should obey, as it denotes a subtype. Since S!float has an alias this to float, it should behave as a float in all circumstances where a float is expected; otherwise, we've got a big problem with alias this on our hands.
Aug 12 2014
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, 12 August 2014 at 15:39:09 UTC, Meta wrote:
 What I mean is that this breaks the Liskov Substitution
 Principle, which alias this should obey, as it denotes a 
 subtype.
 Since S!float has an alias this to float, it should behave as a
 float in all circumstances where a float is expected; otherwise,
 we've got a big problem with alias this on our hands.
IMHO, it was a mistake to add alias this to the language. It's occasionally useful, but it's too dangerous. Implicit conversions wreak havoc with templates, because inevitably what happens is that a type is tested for whether it implicitly converts to a particular type, but then the template is instantiated with the original type, not the implicitly converted one, and then the template frequently fails to compile - or if it does compile, it may do weird things, because you're dealing with a type that doesn't act as expected. If you're dealing with a template which doesn't accept implicit conversions (e.g. isNaN), and the implicit conversion were tested after the actual type failed, and the template was then instantiated with the implicitly converted type, then maybe that could work, but that's not how it works now, and in general, I think alias this is just too dangerous to use. - Jonathan M Davis
Aug 12 2014
parent reply "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
On Tue, Aug 12, 2014 at 04:04:41PM +0000, Jonathan M Davis via
Digitalmars-d-learn wrote:
 On Tuesday, 12 August 2014 at 15:39:09 UTC, Meta wrote:
What I mean is that this breaks the Liskov Substitution
Principle, which alias this should obey, as it denotes a subtype.
Since S!float has an alias this to float, it should behave as a
float in all circumstances where a float is expected; otherwise,
we've got a big problem with alias this on our hands.
IMHO, it was a mistake to add alias this to the language. It's occasionally useful, but it's too dangerous.
I disagree. Alias this is a very powerful tool that we haven't fully leveraged yet. Not only it allows for implicit conversion to native types (which makes it very useful for transparent type wrapping and user-defined numeric types that are on par with built-in types), it also allows for things like transparent safe dereference handling, as I demonstrated some time ago.
 Implicit conversions wreak havoc with templates, because inevitably
 what happens is that a type is tested for whether it implicitly
 converts to a particular type, but then the template is instantiated
 with the original type, not the implicitly converted one, and then the
 template frequently fails to compile - or if it does compile, it may
 do weird things, because you're dealing with a type that doesn't act
 as expected.
That's because the template code was wrongly written. If it expects the basic type, then it should be constrained to take only basic types (is(T==int) instead of is(T : int)). If it wants a member variable to be a basic type, then it should use the converted-to type instead of the original type (if it tests is(T : int) but wants an int, then it should use int as the target type, not T). Problems with carelessly-written template code is no fault of alias this.
 If you're dealing with a template which doesn't accept implicit
 conversions (e.g. isNaN), and the implicit conversion were tested
 after the actual type failed, and the template was then
 instantiated with the implicitly converted type, then maybe that
 could work, but that's not how it works now, and in general, I
 think alias this is just too dangerous to use.
[...] I disagree. If a template function can take an implicitly-converted type, then it should assign the original type to the target type if that's what it wants to use. For example: auto badCode(T)(T t) if (is(T : float)) { T u = 1.0; // does anyone expect this to NOT blow up? u += t; // or this? ... } struct S { float toFloat() { ... } alias toFloat this; } auto r = badCode(S.init); // oops auto goodCode(T)(T t) if (is(T : float)) { float tf = t; // now we're talking float u = 1.0; // now this for sure won't blow up u += tf; ... } auto s = goodCode(S.init); // OK Basically, the root of the problem is that the way C++ and D templates work, the template body is free to assume *anything* about the incoming types, with or without verification (mostly without, IME). As a result, template code often makes unfounded assumptions, like: auto myFunc(T)(T t) if (is(T : float)) { float f = t; // this is about the only safe thing we can // do, given the stated assumptions ... T u; // are we sure the type is instantiable? u = 1.0; // are we sure the type accepts literal assignment? auto r = t + 1.0; // are we sure the result type is anything we expect? ... // etc. } This example is blatantly obvious, but in practice, things are a lot more tricky. Classic example: auto myFunc(R)(R range) if (isInputRange!R) { // what makes us think typeof(range.front) is assignable? auto f = range.front; ... range.popFront(); // This fails on transient ranges, but happily compiles // 'cos isInputRange doesn't ensure the assumption that // assigned values of .front persist beyond .popFront. // Basically, it's an unfounded assumption. if (f == range.front) { ... } } Another typical example of poorly-written template code: auto nextColumn(RoR)(RoR rangeOfRanges) if (isForwardRange!RoR && isInputRange!(ElementType!RoR)) { foreach (subrange; rangeOfRanges) { // what makes us think this has any persistent // effect past this iteration of the loop? subrange.popFront(); } // For all we know, this could just be returning the // original, unmodified range, because // rangeOfRanges.front could be returning temporary // copies of the actual subranges. // // Or it could return an empty range because we forgot // to call .save. But even if we *did* call .save, what // makes us think .popFront() on the subranges has any // persistent effect on either the original *or* the // .save'd range outside the foreach loop? return rangeOfRanges; } The problem is, neither isForwardRange!RoR nor isInputRange!(ElementType!RoR) grant us any foundation at all for the assumptions that the code makes. True, some of these assumptions cannot be expressed as sig constraints, but they should at least be documented up front in bold. Unfortunately, most of the docs I see for such functions are underdocumented, and generally do not state any such assumptions. Correctly-written template code would explicitly test for all properties it wishes to use, and make no blind assumptions about anything. For example: auto goodCode(T)(T t) if (is(T : float) && is(typeof({T t;})) // ensure T is instantiable! && is(typeof(T.init < T.init)) // ensure T is comparable && is(typeof(t = t)) // ensure T is assignable && ... /* etc */) { T u; if (u < t) { ... } u = t; float f = t; // N.B.: *not* "auto f = t" ... // etc. } auto badCode(T)(T t) if (is(T : float)) { float tmp = t + 1.0; // who says the result of + is a float? auto tmp2 = t + 1.0; // this seems to work... T u = tmp2; // how do we know this is valid? if (u < t) ... // how do we know T is comparable? t = u; // how do we know T is assignable? if (u < u.max) // how do we know T.max exists? ... int x = u.ndig; // how do we know T.ndig == cast(float)t.ndig? ... // etc. } tl;dr: there are so many ways template code can go wrong, that I don't it justifies blaming alias this for problems. T -- Why ask rhetorical questions? -- JC
Aug 12 2014
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, 12 August 2014 at 19:03:58 UTC, H. S. Teoh via 
Digitalmars-d-learn wrote:
 tl;dr: there are so many ways template code can go wrong, that 
 I don't
 it justifies blaming alias this for problems.
Allowing implicit conversions makes the problem much worse IMHO. It makes it far too easy to write a template constraint which passes due to the implicit conversion (even if an implicit conversion wasn't explicitly checked for) but then have the function fail to work properly because the implicit conversion never actually takes place within the function (and if the template constraint doesn't explicitly test for an implicit conversion, then the argument that the value should have been explicitly converted doesn't hold). Fortunately, in many cases, the result is a compilation error rather than weird behavior, but in some cases, it will be weird behavior - especially when the code involved is highly templatized and generic. I'm not even vaguely convinced that allowing implicit conversions is worth the pain that they cause with templated code. Sure, they have their uses, and it would be a loss to have nothing like alias this, but on the whole, I'd much rather pay the cost of having no implicit conversions than having to deal with the havoc they wreak on templated code. - Jonathan M Davis
Aug 12 2014
parent reply "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
On Tue, Aug 12, 2014 at 08:23:30PM +0000, Jonathan M Davis via
Digitalmars-d-learn wrote:
 On Tuesday, 12 August 2014 at 19:03:58 UTC, H. S. Teoh via
 Digitalmars-d-learn wrote:
tl;dr: there are so many ways template code can go wrong, that I
don't it justifies blaming alias this for problems.
Allowing implicit conversions makes the problem much worse IMHO. It makes it far too easy to write a template constraint which passes due to the implicit conversion (even if an implicit conversion wasn't explicitly checked for) but then have the function fail to work properly because the implicit conversion never actually takes place within the function (and if the template constraint doesn't explicitly test for an implicit conversion, then the argument that the value should have been explicitly converted doesn't hold). Fortunately, in many cases, the result is a compilation error rather than weird behavior, but in some cases, it will be weird behavior - especially when the code involved is highly templatized and generic.
[...] Y'know, after seeing the recent problem with deprecated functions in template code, and now this, I'm starting to reconsider whether Concepts might have been a better way to go. The good thing about concepts is that the template body won't even compile if it makes unfounded assumptions on the type that aren't given by what the concept defines. In the current template system, code like this would easily compile: auto badCode(T)(T t) { // Does T even support such an operation? Who knows. The // compiler will happily accept this code, and if you // only unittest this function with numeric types, // you'll never know there was a problem. return t+1; } In a concepts-based system, however, this code wouldn't compile, even before you instantiate any templates. You'd have to define a concept that supports the opBinary!"+"(int) operation before the compiler would accept the code. And doing so enforces all incoming types to conform to that concept or be instantly rejected. This causes the templated code to be unable to do anything with the incoming type that isn't explicitly specified in the concept, thus preventing unfounded assumptions. It furthermore solves the alias this problem, because it would be clear to the compiler which concept the function is supposed to be operating on, and if the incoming type implicitly converts to something that implements that concept, then the compiler knows to perform the conversion first before perform an operation on it. Now, it's true that concepts-based systems have their limitations. But I wonder if it's possible to extend a concepts-based system to be more powerful, in the same way D has enhanced and developed C++ templates in novel ways. One way I can think of, that kinda sits between the current duck-typing template system and a full-fledged concepts system, is one where incoming types do not have to declare what concept they implement (thus, a kind of duck-typing), but template functions are not allowed to operate on an incoming type unless they declare a concept it must conform to. Hypothetical syntax: // Concept definition concept InputRange(E) { alias ElementType = E; bool empty; E front; void popFront(); } // Concrete type that implements InputRange(E) // N.B.: don't need to declare what concept it implements: this // makes it compatible with current code. struct MyRange { property bool empty() { ... } property int front() { ... } void popFront() { ... } // N.B.: this is *not* part of the concept definition property MyRange save() { ... } } auto r = rangeFunc(MyRange.init); auto rangeFunc(R : InputRange!E, E)(R range) { while (!range.empty) // OK, .empty defined in InputRange!E { auto e = range.front; // OK, .front defined in InputRange!E if (someCond) { // ERROR: even though MyRange does // define .save, InputRange doesn't, and // we have only declared R to be an // InputRange, so .save is an undefined // operation on R. auto tmp = range.save; } // OK: .popFront is defined in InputRange!E range.popFront(); } return ...; } T -- There are two ways to write error-free programs; only the third one works.
Aug 12 2014
parent Ary Borenszweig <ary esperanto.org.ar> writes:
On 8/12/14, 6:31 PM, H. S. Teoh via Digitalmars-d-learn wrote:
 On Tue, Aug 12, 2014 at 08:23:30PM +0000, Jonathan M Davis via
Digitalmars-d-learn wrote:
 On Tuesday, 12 August 2014 at 19:03:58 UTC, H. S. Teoh via
 Digitalmars-d-learn wrote:
 tl;dr: there are so many ways template code can go wrong, that I
 don't it justifies blaming alias this for problems.
Allowing implicit conversions makes the problem much worse IMHO. It makes it far too easy to write a template constraint which passes due to the implicit conversion (even if an implicit conversion wasn't explicitly checked for) but then have the function fail to work properly because the implicit conversion never actually takes place within the function (and if the template constraint doesn't explicitly test for an implicit conversion, then the argument that the value should have been explicitly converted doesn't hold). Fortunately, in many cases, the result is a compilation error rather than weird behavior, but in some cases, it will be weird behavior - especially when the code involved is highly templatized and generic.
[...] Y'know, after seeing the recent problem with deprecated functions in template code...
Duck typing FTW
Aug 12 2014