Search code examples
d

D: Unexpected output when using a delegate and EnumMembers


I have some rather odd behavior in my D program that I've narrowed down to this:

import std.algorithm;
import std.stdio;
import std.traits;

enum E { a, b, c };

struct S { E e; };

void main()
{
    immutable(S)[] source = [ S(E.a), S(E.a), S(E.b) ];
    foreach (e; EnumMembers!E)
    {
        size_t c = count!(x => x.e == e)(source);
        writeln(e, " -> ", c);
    }
}

I would expect the output of this program to be something along the lines of:

a -> 2
b -> 1
c -> 0

But the actual result is:

a -> 2
b -> 2
c -> 2

Curiously, changing the for loop to foreach (e; [ E.a, E.b, E.c ]) produces my expected output. Using foreach (e; [ EnumMembers!E ]) also produces my expected result, so clearly my use of the range from EnumMemebers is the problem here...I just don't know why.

I am clearly doing something wrong, but I have no idea what and would appreciate some insight.

My compiler is DMD64 D Compiler v2.059 on Linux.


EDIT: This has the exact same behavior with GDC 4.6.3, so it can't be a compiler bug.


EDIT: By moving the count call to a separate function:

size_t counte(Range)(E e, Range src)
{
    return count!(x => x.e == e)(src);
}

and changing c's initialization to size_t c = counte(e, source);, the program works as I would expect.


Solution

  • Short and incomplete answer that may help with direction to dig for:

    EnumMembers!T is a type tuple ( http://dlang.org/tuple.html -> Type Tuple ), not an expression tuple or array. When you use [ EnumMembers!T ] syntax, tuple is used as an initializer list at compile time and regular array is created, providing expected behavior.

    Now, if you use type tuple in a foreach statement as-is, things gotta get interesting. There is a special case foreach for tuples: http://dlang.org/statement.html -> Foreach over Tuples. And in case of expression type tuples ( sorry for weird wording, but unfortunately, that is how it is named in D ) it does not really create a variable - it just replaces all usages of e with expression taken from type tuple.

    And here we go - e in lambda is just replaced with expression from tuple and so this lambda is NOT a delegated. I have checked it's typeof, it is "bool function(S x) pure nothrow". Guess it is created the very first time it is used, remembering e expression in lambda code and then just using it as-is.

    I need someone else to comment about is it a bug, misfeature or works as intended.