Thread overview
T[] opIndex() Error: .. signal 11
Oct 03, 2023
Joel
Oct 03, 2023
ryuukk_
Oct 03, 2023
Paul Backus
Oct 05, 2023
ag0aep6g
October 03, 2023

The following program crashes, but doesn’t if I change (see title) T[] to auto. The program doesn’t even use that method/function. What’s the story?

// Adding program - literal functions

import std;

struct List(T) {
    class Node {
        T data;
        Node next;
        this(T data) {
            this.data=data;
        }
    }
    string title;
    size_t length;
    Node head;
    this(string title, T[] data...) {
        this.title=title;
        length=data.length;
        if (length) {
            head=new Node(data[0]);
            auto cur=head;
            data[1..$].each!((d) {
                cur.next=new Node(d);
                cur=cur.next;
            });
        }
    }
    bool empty() { return head is null; }
    auto ref front() { return head.data; }
    void popFront() { head=head.next; }
    T[] opIndex() {
        return this.array;
    }
    auto opDollar() {
        return length;
    }
}

void main(string[] args) {
    args.popFront;
    List!int ints;
    if (args.length) {
        ints=List!int(args[0], args[1..$].to!(int[]));
    } else{
        ints=List!int("Car, and Date numbers", 1979,9,3,4,5);
    }
    stdout.write(ints.title, ": ");
    ints.each!((i, d) {
        stdout.write(d, i+1<ints.length ? "+" : "=");
    });
    writeln(ints.sum);
}
October 03, 2023

On Tuesday, 3 October 2023 at 15:12:34 UTC, Joel wrote:

>

The following program crashes, but doesn’t if I change (see title) T[] to auto. The program doesn’t even use that method/function. What’s the story?

// Adding program - literal functions

import std;

struct List(T) {
    class Node {
        T data;
        Node next;
        this(T data) {
            this.data=data;
        }
    }
    string title;
    size_t length;
    Node head;
    this(string title, T[] data...) {
        this.title=title;
        length=data.length;
        if (length) {
            head=new Node(data[0]);
            auto cur=head;
            data[1..$].each!((d) {
                cur.next=new Node(d);
                cur=cur.next;
            });
        }
    }
    bool empty() { return head is null; }
    auto ref front() { return head.data; }
    void popFront() { head=head.next; }
    T[] opIndex() {
        return this.array;
    }
    auto opDollar() {
        return length;
    }
}

void main(string[] args) {
    args.popFront;
    List!int ints;
    if (args.length) {
        ints=List!int(args[0], args[1..$].to!(int[]));
    } else{
        ints=List!int("Car, and Date numbers", 1979,9,3,4,5);
    }
    stdout.write(ints.title, ": ");
    ints.each!((i, d) {
        stdout.write(d, i+1<ints.length ? "+" : "=");
    });
    writeln(ints.sum);
}

Remove:
It didn't segfault for me, but it still didn't print what's in the ints, but if you remove the following code:

   T[] opIndex() {
        return this.array;
    }
    auto opDollar() {
        return length;
    }

..then it'll work

Sounds like a bug.. i don't use these D features, maybe someone else have more insights?

Either way, what a terrible experience

October 03, 2023

On 10/3/23 11:12 AM, Joel wrote:

>

The following program crashes, but doesn’t if I change (see title) T[] to auto. The program doesn’t even use that method/function. What’s the story?

It's a stack overflow.

when doing foreach on your type, the compiler always uses a slice first if it compiles and is a valid range.

So foreach(x; ints) really translates to foreach(x; ints[]).

Normally not a problem. But your opIndex() is calling this.array. What does this.array do? a foreach on your type. Which calls opIndex, which calls array, which calls opIndex, etc.

When you make it auto, well, then inside the array function, it won't use the opIndex (because clearly, the type hasn't been determined). And so it goes with the range functions without first doing a slice.

But then outside the type, now that opIndex type has been inferred, it can now use foreach(x; ints[]), and that goes back to the regular mechanism.

A minimized case is here:

struct S
{
    int front() => 1;
    void popFront() {}
    bool empty() => true;

    auto opIndex() {
        foreach(x; this) {}
        return int[].init;
    }
}

void main()
{
    S s;
    foreach(x; s) {}
}

If you run this on run.dlang.io, and click the "AST" button, you will get this for the type and the main function:

import object;
struct S
{
	int front()
	{
		return 1;
	}
	void popFront()
	{
	}
	bool empty()
	{
		return true;
	}
	auto @system int[] opIndex()
	{
		{
			S __r2 = this;
			for (; !__r2.empty(); __r2.popFront())
			{
				int x = __r2.front();
			}
		}
		return null;
	}
}
void main()
{
	S s = 0;
	{
		scope int[] __r3 = s.opIndex()[];
		ulong __key4 = 0LU;
		for (; __key4 < __r3.length; __key4 += 1LU)
		{
			int x = __r3[__key4];
		}
	}
	return 0;
}

Note the difference in how the foreach code is lowered. Inside opIndex, it's lowered to the range functions. Outside, it uses the slice operator to switch to iterating a scope int[].

If you now switch the auto to int[], it's a segfault, because now the opIndex has a concrete return type, and it can use the opIndex, inside opIndex.

I really think the implicit slice should be revisited. It shouldn't happen in this case.

-Steve

October 03, 2023

On Tuesday, 3 October 2023 at 17:05:46 UTC, Steven Schveighoffer wrote:

>
void main()
{
	S s = 0;
	{
		scope int[] __r3 = s.opIndex()[];
		ulong __key4 = 0LU;
		for (; __key4 < __r3.length; __key4 += 1LU)
		{
			int x = __r3[__key4];
		}
	}
	return 0;
}

Note the difference in how the foreach code is lowered. Inside opIndex, it's lowered to the range functions. Outside, it uses the slice operator to switch to iterating a scope int[].

Naturally, this lowering is completely absent from the language spec's section on foreach. According to the spec, the only ways to iterate over a struct type are opApply and the input range interface.

I think it would probably be less confusing to have both opApply and empty/front/popFront take precedence over this lowering, but I have no idea how much existing code would be broken by such a change. At the very least, though, this needs to be documented.

October 05, 2023
On 03.10.23 20:26, Paul Backus wrote:
> Naturally, this lowering is completely absent from [the language spec's section on `foreach`.][1] According to the spec, the only ways to iterate over a `struct` type are `opApply` and the input range interface.
> 
> I think it would probably be less confusing to have both `opApply` and `empty`/`front`/`popFront` take precedence over this lowering, but I have no idea how much existing code would be broken by such a change. At the very least, though, this needs to be documented.
> 
> [1]: https://dlang.org/spec/statement.html#foreach-statement

For some further reading, there's an open issue about the unexpected slicing: https://issues.dlang.org/show_bug.cgi?id=14619
October 05, 2023

On 10/5/23 1:49 AM, ag0aep6g wrote:

>

For some further reading, there's an open issue about the unexpected slicing: https://issues.dlang.org/show_bug.cgi?id=14619

Thank you I had forgotten about that issue!

-Steve