www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Inout unclearness

reply "Max Klimov" <klimroot mail.ru> writes:
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
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
parent reply "Max Klimov" <klimroot mail.ru> writes:
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
next sibling parent reply "Kagamin" <spam here.lot> writes:
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
parent reply "Max Klimov" <klimroot mail.ru> writes:
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
next sibling parent "Kagamin" <spam here.lot> writes:
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
prev sibling parent reply "Kagamin" <spam here.lot> writes:
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
parent reply "Max Klimov" <klimroot mail.ru> writes:
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
parent reply "Kagamin" <spam here.lot> writes:
On Saturday, 25 July 2015 at 02:19:40 UTC, Max Klimov wrote:
 Where is unsafety? Code is the same for 3 versions
You 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
parent reply "Max Klimov" <klimroot mail.ru> writes:
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
parent "Kagamin" <spam here.lot> writes:
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
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 7/20/15 8:07 PM, Max Klimov wrote:
 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.
Sure, you can do this via unit tests.
 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
parent "Max Klimov" <klimroot mail.ru> writes:
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