www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 2543] New: foreach's index behaves differently for every type

http://d.puremagic.com/issues/show_bug.cgi?id=2543

           Summary: foreach's index behaves differently for every type
           Product: D
           Version: 2.022
          Platform: PC
        OS/Version: Linux
            Status: NEW
          Keywords: diagnostic, spec
          Severity: minor
          Priority: P2
         Component: DMD
        AssignedTo: bugzilla digitalmars.com
        ReportedBy: lat7h virginia.edu


The behavior of foreach index differs depending on what type of thing is being 
iterated.  
 * For static and dynamic arrays, index cannot be ref but always acts like it
is ref.
 * For associative arrays, index cannot be ref and always act accordingly.
 * For structs and classes, index can be either ref or non-ref,and behaves
accordingly.  However, ref behavior cannot be blocked it undesirable.

While the current system works, it is needlessly inconsistent.

The full behavior is exposed by the following example:
----
void plain(T)(T iterable) { foreach (i, v; iterable) write(i++," "); }
void refer(T)(T iterable) { foreach (ref i, v; iterable) write(i++," "); }
struct allow_ref {
    int opApply(int delegate(inout uint, inout int) dg) {
        int res; for (uint i=0; i<4; ++i) res = dg(i,i+1); return res;
    }
}
struct ignore_ref {
    int opApply(int delegate(inout uint, inout int) dg) {
        int res; for (uint i=0; i<4; ++i) res = dg(i+0,i+1); return res;
    }
}
struct fail_on_write {
    int opApply(int delegate(inout const(uint), inout int) dg) {
        int res; for (uint i=0; i<4; ++i) res = dg(i,i+1); return res;
    }
}

void main(string[] args) {
    int[] array = [1,2,3,4]; // int[4] behaves the same
    plain(array);     // OK: "0 2"
    refer(array);     // Error: foreach: key cannot be out or ref

    int[int] assoc = [0:1,1:2,2:3,3:4];
    plain(assoc);     // OK: "0 1 2 3"
    refer(assoc);     // Error: foreach: index cannot be ref

    allow_ref custom1;
    plain(custom1);   // OK: "0 1 2 3"
    refer(custom1);   // OK: "0 2"

    ignore_ref custom2;
    plain(custom2);   // OK: "0 1 2 3"
    refer(custom2);   // OK: "0 1 2 3"

    fail_on_write custom3;
    plain(custom3);   // Error: plain.foreachbody23.index cannot modify const
    refer(custom3);   // Error: refer.foreachbody23.index cannot modify const
}
----

A minor note: int[] calls the index "key"; int[int] calls it "index". These are
either inconsistent or backward.  Also, foreach can't have out-qualified
elements, so the "out or" in the array error message seems odd.

If I want to build something like an associative array, where the index type is
mutable but the index itself cannot be modified to control the iteration, no
good solution exists:
 * making the delegate not accept ref input fails to match foreach call
 * making the index const prevents even local modifications
 * making the index tamper-proof fails to let the user know what is happening

I propose at least the following simple fixes:
 * foreach(i,v; array) should have "i" not control iteration
 * foreach(ref i,v; array) should be allowed and have the behavior currently
exhibited by foreach(i,v; array) behavior

Preventing foreach(ref i,v; structOrClass) is less obvious.  Some
possibilities:
 * non-ref foreach loops search first for non-ref delegates, falling back on a
ref delegate is they are not available.  This keeps the benefit of being albe
to write a single method for all four ref/nonref cases and allows the
programmer to rule out certain ref usages at compile time.  I have no idea how
difficult it would be to implement.  While we're at it, I'd love to see the
two-parameter delegate opApply used in lieu of one-parameter if the later is
lacking...
 * non-ref foreach currently uses ref delegates without propagating writes back
into the opApply body; if this implicit copy removed the const() (where allowed
by the typing rules) the fail_on_write struct would work (for some index
types).
 * each of the six foreach types (v;A) (ref v;A) (i,v;A) (ref i,v;A) (i,ref
v;A) (ref i, ref v;A) could create a different delegate signature, so that a
fully foreach-enabled struct has six more-or-less identical opApply methods
with slightly different parameters.


-- 
Dec 27 2008