digitalmars.D - Inout unclearness
- Max Klimov (47/47) Jul 11 2015 I have some questions about "inout" qualifier. I treated this
- Steven Schveighoffer (46/64) Jul 13 2015 So a variable cannot be inout in a struct, because once it leaves the
- Max Klimov (16/21) Jul 20 2015 Sure, however, it is still not the same as using inout.
- Kagamin (3/9) Jul 21 2015 So you have 4 different codes, which will you pick as the result?
- Max Klimov (7/9) Jul 21 2015 I tried to explain my vision above. I want a compiler to work
- Kagamin (3/4) Jul 22 2015 That's unsafe. Unsafe behavior should require a cast or something
- Kagamin (10/10) Jul 22 2015 How about this?
- Max Klimov (5/17) Jul 24 2015 Where is unsafety? Code is the same for 3 versions, it guarantees
- Kagamin (4/6) Jul 26 2015 The example works like inout without inout, so you can use types
- Max Klimov (15/18) Jul 27 2015 Does constBack imply to have several static if constructions,
- Kagamin (4/6) Jul 28 2015 Function `f` in the example has only one instance of generated
- Steven Schveighoffer (16/36) Jul 21 2015 Unfortunately, this is true. I don't know how this could be fixed, and
- Max Klimov (19/31) Jul 21 2015 So the way that I see it is that "inout" is preserved until code
I have some questions about "inout" qualifier. I treated this qualifier as duplicated code reducer without using templates. But it is a real part of type system with its own casting rules. Inside an inout function we have special type "inout(T)" with several restrictions. So the first question is: should I keep in mind that my generalized code can meet inout type and how to work with it? For example, I have problems with the following code that, I suppose, should work. class A {} A foo(A x) // works { Rebindable!(typeof(return)) r; r = x; return r; } const(A) foo(const(A) x) // works { Rebindable!(typeof(return)) r; r = x; return r; } immutable(A) foo(immutable(A) x) // works { Rebindable!(typeof(return)) r; r = x; return r; } inout(A) bar(inout(A) x) // compilation error { Rebindable!(typeof(return)) r; r = x; return r; } Rebindable doesn't handle the case of inout type and I don't think that it can do it because it is possible to cast to inout or to keep inout variables only inside the proper inout function. So, I have to make some silly workarounds with castings in this case. The same situation happens in several places in phobos when arguments type is deductible. For example, std.array.replaceInPlace(...), std.array.join(...), etc, can not be used with inout(T) parameters. The second question: if I want to provide inout function but it fails like in the example above, what is common practice? Just use casting? Or make a templated version and provide hand-writing code for all cases (mutable, const, immutable)? Or something wrong with inout qualifier?
Jul 11 2015
On 7/12/15 1:03 AM, Max Klimov wrote:inout(A) bar(inout(A) x) // compilation error { Rebindable!(typeof(return)) r; r = x; return r; } Rebindable doesn't handle the case of inout type and I don't think that it can do it because it is possible to cast to inout or to keep inout variables only inside the proper inout function.So a variable cannot be inout in a struct, because once it leaves the function, it's not clear how to cast that part of the struct to something else. In other words, a struct like this: struct X(T) { T t; } X!(int) x1; X!(const(int)) x2 = x1; Techincally, the assignment should be allowed, but the compiler cannot see into the template to see if it's actually the same, or even if the template parameter applies to anything in the struct. One can do something like: struct X(T) { static if(is(T == const)) bool screwup; T t; } With inout, it's even more tricky, because the compiler has to ensure it's not inout when it leaves the function. And it's not possible in some cases to do this. At the end of the day, this comes down to D not supporting tail-const classes. If that was supported in the language, this is all doable.So, I have to make some silly workarounds with castings in this case. The same situation happens in several places in phobos when arguments type is deductible. For example, std.array.replaceInPlace(...), std.array.join(...), etc, can not be used with inout(T) parameters.Right, this is because it uses generated structs internally.The second question: if I want to provide inout function but it fails like in the example above, what is common practice? Just use casting? Or make a templated version and provide hand-writing code for all cases (mutable, const, immutable)? Or something wrong with inout qualifier?You can simply use a template function without hand-written cases. This is what inout was meant to replace. In your example: T bar(T)(T t) if(is(Unqual!T : A)) { // same implementation } Of course, this doesn't work for inout!A as a parameter, if that is what you have :) But you could specifically cast it to/from const, or do something like: T bar(T)(T t) if(is(Unqual!T : A)) { static if(is(T == inout)) return cast(T)(.bar(cast(const(Unqual!T))t)); else { // real implementation } } -Steve
Jul 13 2015
On Monday, 13 July 2015 at 13:50:02 UTC, Steven Schveighoffer wrote:With inout, it's even more tricky, because the compiler has to ensure it's not inout when it leaves the function. And it's not possible in some cases to do this.You can simply use a template function without hand-written cases. This is what inout was meant to replace.Sure, however, it is still not the same as using inout. Firstly, your function should be instantiated somewhere for mutable T, const T and immutable T. Otherwise, compilation errors will be postponed until the code usage. Secondly, template function can not be virtual. I'm wondering why it is needed to have special casting rules and other restrictions for inout if people should treat inout as wildcard for mutable, immutable and const. As far as I can imagine, a compiler should generate one single object code for inout function. Is it possible just to check validity of an inout(T) function for T, const(T) and immutable(T) and then, if success, generate the code? (keeping in mind that you can execute another inout functions inside this one). Am I missing something? -Max
Jul 20 2015
On Tuesday, 21 July 2015 at 00:07:11 UTC, Max Klimov wrote:I'm wondering why it is needed to have special casting rules and other restrictions for inout if people should treat inout as wildcard for mutable, immutable and const.It's unclear how to check that people did what they should.Is it possible just to check validity of an inout(T) function for T, const(T) and immutable(T) and then, if success, generate the code?So you have 4 different codes, which will you pick as the result?
Jul 21 2015
On Tuesday, 21 July 2015 at 08:30:19 UTC, Kagamin wrote:So you have 4 different codes, which will you pick as the result?I tried to explain my vision above. I want a compiler to work with inout after template instantiations and so on. In this case I hope generated code will be the same. I want to avoid the restrictions for casting and keeping inout as a member outside inout function. - Max
Jul 21 2015
On Wednesday, 22 July 2015 at 06:14:04 UTC, Max Klimov wrote:In this case I hope generated code will be the same.That's unsafe. Unsafe behavior should require a cast or something like that.
Jul 22 2015
How about this? const(A) f(const(B) b); auto constBack(R,P)(const R r, P p); Then invoke: B b; A a = f(b).constBack(b); So `f` takes const parameters and returns const result, you feed the result to the function `constBack` with one of parameters and it infers constness from that parameter and casts the result to the inferred constness.
Jul 22 2015
On Wednesday, 22 July 2015 at 07:49:07 UTC, Kagamin wrote:That's unsafe. Unsafe behavior should require a cast or something like that.Where is unsafety? Code is the same for 3 versions, it guarantees immutability of data because it was checked for const(T) and immutable(T).How about this? const(A) f(const(B) b); auto constBack(R,P)(const R r, P p); Then invoke: B b; A a = f(b).constBack(b); So `f` takes const parameters and returns const result, you feed the result to the function `constBack` with one of parameters and it infers constness from that parameter and casts the result to the inferred constness.Sorry, I didn't quite get it. What is this example for?
Jul 24 2015
On Saturday, 25 July 2015 at 02:19:40 UTC, Max Klimov wrote:Where is unsafety? Code is the same for 3 versionsYou only hope it's the same.Sorry, I didn't quite get it. What is this example for?The example works like inout without inout, so you can use types with templates and virtual functions.
Jul 26 2015
On Sunday, 26 July 2015 at 13:06:50 UTC, Kagamin wrote:The example works like inout without inout, so you can use types with templates and virtual functions.Does constBack imply to have several static if constructions, function overloadings or template specializations? I mean does this help to reduce the amount of code? And it also sets aside the discussion about casting const away (http://forum.dlang.org/thread/riiehqozpkyluhhifwha forum.dlang.org).You only hope it's the same.I believe that generated code does not distinguish constness of object. Moreover, currently inout goes with one code, so it is possible. All my questions and talks rose from inout restrictions on casting and keeping inside classes and structs. I'm not sure that they are needed. As for now, inout is quite useless for the cases little bit more complex than "inout(A) getData() inout { return data; }". It leads to templates overusage and code duplication. I'm not against inout, I'm for its improvement.
Jul 27 2015
On Monday, 27 July 2015 at 20:13:31 UTC, Max Klimov wrote:I mean does this help to reduce the amount of code?Function `f` in the example has only one instance of generated code.I'm not against inout, I'm for its improvement.Improvement is welcome.
Jul 28 2015
On 7/20/15 8:07 PM, Max Klimov wrote:On Monday, 13 July 2015 at 13:50:02 UTC, Steven Schveighoffer wrote:Sure, you can do this via unit tests.With inout, it's even more tricky, because the compiler has to ensure it's not inout when it leaves the function. And it's not possible in some cases to do this.You can simply use a template function without hand-written cases. This is what inout was meant to replace.Sure, however, it is still not the same as using inout. Firstly, your function should be instantiated somewhere for mutable T, const T and immutable T. Otherwise, compilation errors will be postponed until the code usage.Secondly, template function can not be virtual.Unfortunately, this is true. I don't know how this could be fixed, and inout certainly does help in this regard.I'm wondering why it is needed to have special casting rules and other restrictions for inout if people should treat inout as wildcard for mutable, immutable and const.Again, it has nothing to do with code generation, it has to do with type conversion. The compiler doesn't know how to convert Rebindable!(inout(T)) to something that can be returned from an inout function. We haven't figured out how to tell it what to do there, so it has to give up.As far as I can imagine, a compiler should generate one single object code for inout function. Is it possible just to check validity of an inout(T) function for T, const(T) and immutable(T) and then, if success, generate the code? (keeping in mind that you can execute another inout functions inside this one). Am I missing something?It has to not only successfully generate code, the code itself has to be exactly the same for all 3 versions (and actually, if you have multiple inout parameters, it potentially has to run 3^parameter tests). It could potentially do this, but inout works just as effectively with the current rules. It still does not provide a way to convert a struct with an inout member to a struct without one. -Steve
Jul 21 2015
On Tuesday, 21 July 2015 at 12:53:37 UTC, Steven Schveighoffer wrote:Again, it has nothing to do with code generation, it has to do with type conversion. The compiler doesn't know how to convert Rebindable!(inout(T)) to something that can be returned from an inout function. We haven't figured out how to tell it what to do there, so it has to give up.So the way that I see it is that "inout" is preserved until code generation. For example, Rebindable can keep inout(T) in the internals. Then "inout" should be replaced by compiler with 3 versions of type (const, mutable, immutable) for a check. Then the code will be generated one time. One of the problems here is that, for example, Rebindable!(const(T)) can be specialized and have different behavior, unlike Rebindable(inout(T)) for the case inout(T) = const(T).It has to not only successfully generate code, the code itself has to be exactly the same for all 3 versions (and actually, if you have multiple inout parameters, it potentially has to run 3^parameter tests). It could potentially do this, but inout works just as effectively with the current rules. It still does not provide a way to convert a struct with an inout member to a struct without one.Yes, the code itself will be the same, I meant just checks. 3^n possible checks -- that is an excellent point, can be a problem (as well as several other possible problems that I didn't take into account). Finally, I'm sure that there are a lot of reasons to make inout in the present way and I can accept it (regardless of the fact that inout doesn't fit now for cases more complex than trivial ones). Of course, it would be great if D type system is perfect some day :)
Jul 21 2015