January 08, 2018
On Sun, Jan 07, 2018 at 10:39:19PM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
> On 1/6/18 6:25 PM, Ali Çehreli wrote:
> > Is 'static foreach' sufficient for all needs or is there any value for regular foreach over compile-time sequences?
> 
> If you use continues or breaks, then you need to switch to gotos if using static foreach, as it does not support them directly.
[...]

Are you sure?  I was under te impression that it does support continues and breaks -- but only if they are labelled, because of a syntactic ambiguity otherwise.


T

-- 
"Hi." "'Lo."
January 08, 2018
On Monday, January 08, 2018 06:27:12 H. S. Teoh via Digitalmars-d-learn wrote:
> On Sun, Jan 07, 2018 at 10:39:19PM -0500, Steven Schveighoffer via
Digitalmars-d-learn wrote:
> > On 1/6/18 6:25 PM, Ali Çehreli wrote:
> > > Is 'static foreach' sufficient for all needs or is there any value for regular foreach over compile-time sequences?
> >
> > If you use continues or breaks, then you need to switch to gotos if using static foreach, as it does not support them directly.
>
> [...]
>
> Are you sure?  I was under te impression that it does support continues and breaks -- but only if they are labelled, because of a syntactic ambiguity otherwise.

It does support them if they're labeled. I did it just the other day. Originally, that code was not using static foreach (though in both cases, it was over an AliasSeq of template arguments), and I'd used a normal break (which compiles just fine with a non-static foreach) and been very confused about why my tests were failing. since for whetever reason, it didn't occur to me when I was writing it that break would break out of the foreach (probably since I was thinking of it as a compile-time thing and not as a loop that would be run, but I don't know; it seems kind of dumb in retrospect either way). Since my tests failed miserably, I realized something was wrong and fixed it (though man was it confusing to begin with), but I was later able to switch that foreach to static foreach without any breakage. However, if I remove the label now, then it won't compile, since it's a static foreach. So, if I'd used a static foreach from the get-go, it would have actually caught a bug for me before I even ran my tests.

But regardless, labeled break definitely works within a static foreach, and I expect that a labeled continue does as well, but I haven't tried it.

- Jonathan M Davis


January 09, 2018
On 1/8/18 9:27 AM, H. S. Teoh wrote:
> On Sun, Jan 07, 2018 at 10:39:19PM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
>> On 1/6/18 6:25 PM, Ali Çehreli wrote:
>>> Is 'static foreach' sufficient for all needs or is there any value
>>> for regular foreach over compile-time sequences?
>>
>> If you use continues or breaks, then you need to switch to gotos if
>> using static foreach, as it does not support them directly.
> [...]
> 
> Are you sure?  I was under te impression that it does support continues
> and breaks -- but only if they are labelled, because of a syntactic
> ambiguity otherwise.

I thought it only worked for constructs outside the static foreach (like switch).

testing...

Nope, doesn't work. The ambiguity is that if you have a breakable or continuable construct outside a static foreach (e.g. switch), then you may believe that the break statement is affecting the foreach (in fact, that is how tuple-foreach works), but you are actually affecting the outer construct. The extra requirement is to help you realize the implication. It may be removed in the future.

I may have been misleading when I made my first comment. What I mean is that you *can't* break or continue a static foreach, even with labels. However, you *can* do it to a standard foreach over a tuple. This may be one reason you want to use a tuple-foreach over a static foreach.

-Steve
January 09, 2018
On 1/8/18 3:07 PM, Jonathan M Davis wrote:
> But regardless, labeled break definitely works within a static foreach, and
> I expect that a labeled continue does as well, but I haven't tried it.

I didn't mean it that way, see my reply to H.

-Steve
January 09, 2018
On Tue, Jan 09, 2018 at 10:57:03AM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
> On 1/8/18 9:27 AM, H. S. Teoh wrote:
> > On Sun, Jan 07, 2018 at 10:39:19PM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
> > > On 1/6/18 6:25 PM, Ali Çehreli wrote:
> > > > Is 'static foreach' sufficient for all needs or is there any value for regular foreach over compile-time sequences?
> > > 
> > > If you use continues or breaks, then you need to switch to gotos if using static foreach, as it does not support them directly.
> > [...]
> > 
> > Are you sure?  I was under te impression that it does support continues and breaks -- but only if they are labelled, because of a syntactic ambiguity otherwise.
> 
> I thought it only worked for constructs outside the static foreach
> (like switch).
> 
> testing...
> 
> Nope, doesn't work.

Grrr... I thought it did, but you're right, attempting to break the static foreach with a label gets this compile error:

-----
test.d(7): Error: enclosing label FE for break not found
-----


> The ambiguity is that if you have a breakable or continuable construct outside a static foreach (e.g. switch), then you may believe that the break statement is affecting the foreach (in fact, that is how tuple-foreach works), but you are actually affecting the outer construct.

Yes, that's the ambiguity I was referring to. :-)


> The extra requirement is to help you realize the implication. It may be removed in the future.

I vaguely remember Timon mentioning something about implementing static break / static continue, and somehow I thought the labelled break / labelled continue was supposed to be it. Or at least, they are stand-ins until static break/continue are implemented.  Is that no longer on the table?


> I may have been misleading when I made my first comment. What I mean is that you *can't* break or continue a static foreach, even with labels. However, you *can* do it to a standard foreach over a tuple. This may be one reason you want to use a tuple-foreach over a static foreach.
[...]

Actually, that's wrong too. Tuple-foreach does not interpret break/continue either. Here's a proof:

	alias Seq(A...) = A;
	foreach (i; Seq!(0, 1, 2, 3)) {
		static if (i==2)
			break;
		static assert(i < 3); // will fail on the 4th iteration
	}

What actually happens is that all iterations are unrolled, then the unreachable iterations are elided by the optimizer during codegen. The foreach itself is not affected by break/continue at all.


T

-- 
Дерево держится корнями, а человек - друзьями.
January 09, 2018
On 1/9/18 11:35 AM, H. S. Teoh wrote:
> On Tue, Jan 09, 2018 at 10:57:03AM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
>> I may have been misleading when I made my first comment. What I mean
>> is that you *can't* break or continue a static foreach, even with
>> labels. However, you *can* do it to a standard foreach over a tuple.
>> This may be one reason you want to use a tuple-foreach over a static
>> foreach.
> [...]
> 
> Actually, that's wrong too. Tuple-foreach does not interpret
> break/continue either. Here's a proof:
> 
> 	alias Seq(A...) = A;
> 	foreach (i; Seq!(0, 1, 2, 3)) {
> 		static if (i==2)
> 			break;
> 		static assert(i < 3); // will fail on the 4th iteration
> 	}
> 
> What actually happens is that all iterations are unrolled, then the
> unreachable iterations are elided by the optimizer during codegen. The
> foreach itself is not affected by break/continue at all.

A break or continue is simply a goto underneath. A goto in an unrolled loop isn't much different than a goto in a um... rolled loop :) It's just that there are copies of each loop body, and the gotos need copies of the labels.

So no, it's not "interpreted" by the foreach statement, but the foreach statement provides the anchors for the goto label targets.

e.g.:

int x;

foreach(i; Seq!(0, 1, 2, 3)) {
   x += i;
   static if(i % 2) continue;
   x *= i;
}

=>

int x;
{
   x += 0;
   x *= 0;
}
{
   x += 1;
   goto label1;
   x *= 1;
}
{
label1:
   x += 2;
   x *= 2;
}
{
   x += 3;
   goto label2;
   x *= 3;
}
label2:

And then of course, the optimizer weeds out the unreachable statements. Doing this with static foreach wouldn't be as pleasant. You'd have to branch the entire loop body, or use a goto in the case of a break.

-Steve
January 09, 2018
On Tue, Jan 09, 2018 at 02:24:11PM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote: [...]
> A break or continue is simply a goto underneath. A goto in an unrolled loop isn't much different than a goto in a um... rolled loop :) It's just that there are copies of each loop body, and the gotos need copies of the labels.
> 
> So no, it's not "interpreted" by the foreach statement, but the foreach statement provides the anchors for the goto label targets.

True.


[...]
> And then of course, the optimizer weeds out the unreachable statements.  Doing this with static foreach wouldn't be as pleasant. You'd have to branch the entire loop body, or use a goto in the case of a break.
[...]

If there were a hypothetical `static continue` or `static break` that's recognized by the static foreach unroller, we could in theory automate this branching in the compiler itself, e.g., by deleting the AST nodes that would be skipped.  Of course, the syntax need not be `static continue`; if there were a way to overload `break LABEL;` for the same purpose, i.e., have the static foreach unroller inspect the label to see if it is referring to the static foreach itself, then this would work.

But I don't know the static foreach implementation enough to be able to tell whether this is actually possible at the time static foreach is processed, or whether there may be some chicken-and-egg problem with inspecting the target of a break/continue before semantic or whatever.


T

-- 
Государство делает вид, что платит нам зарплату, а мы делаем вид, что работаем.
January 09, 2018
On 1/9/18 2:31 PM, H. S. Teoh wrote:
> On Tue, Jan 09, 2018 at 02:24:11PM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
> [...]
>> A break or continue is simply a goto underneath. A goto in an unrolled
>> loop isn't much different than a goto in a um... rolled loop :) It's
>> just that there are copies of each loop body, and the gotos need
>> copies of the labels.
>>
>> So no, it's not "interpreted" by the foreach statement, but the
>> foreach statement provides the anchors for the goto label targets.
> 
> True.
> 
> 
> [...]
>> And then of course, the optimizer weeds out the unreachable
>> statements.  Doing this with static foreach wouldn't be as pleasant.
>> You'd have to branch the entire loop body, or use a goto in the case
>> of a break.
> [...]
> 
> If there were a hypothetical `static continue` or `static break` that's
> recognized by the static foreach unroller, we could in theory automate
> this branching in the compiler itself, e.g., by deleting the AST nodes
> that would be skipped.  Of course, the syntax need not be `static
> continue`; if there were a way to overload `break LABEL;` for the same
> purpose, i.e., have the static foreach unroller inspect the label to see
> if it is referring to the static foreach itself, then this would work.
> 
> But I don't know the static foreach implementation enough to be able to
> tell whether this is actually possible at the time static foreach is
> processed, or whether there may be some chicken-and-egg problem with
> inspecting the target of a break/continue before semantic or whatever.

Yeah, I think in terms of static foreach, it's not a straightforward problem. Because the compiler may not know enough information at the time to figure out whether it should still keep generating code.

For example:

static foreach(i; 0 .. 5)
{
   if(i == 3) static break;
   static assert(i < 3);
}

How does it know whether the static break should be "executed" at compile-time if it hasn't evaluated the if-statement? The code would have to have no runtime branches to make sure that static break can be evaluated at compile-time.

And this still puts it at a disadvantage when compared to tuple-foreach, at least as far as break/continue are concerned.

-Steve
January 09, 2018
On Tue, Jan 09, 2018 at 03:26:32PM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
> On 1/9/18 2:31 PM, H. S. Teoh wrote:
[...]
> > If there were a hypothetical `static continue` or `static break` that's recognized by the static foreach unroller, we could in theory automate this branching in the compiler itself, e.g., by deleting the AST nodes that would be skipped.  Of course, the syntax need not be `static continue`; if there were a way to overload `break LABEL;` for the same purpose, i.e., have the static foreach unroller inspect the label to see if it is referring to the static foreach itself, then this would work.
> > 
> > But I don't know the static foreach implementation enough to be able to tell whether this is actually possible at the time static foreach is processed, or whether there may be some chicken-and-egg problem with inspecting the target of a break/continue before semantic or whatever.
> 
> Yeah, I think in terms of static foreach, it's not a straightforward problem. Because the compiler may not know enough information at the time to figure out whether it should still keep generating code.
> 
> For example:
> 
> static foreach(i; 0 .. 5)
> {
>    if(i == 3) static break;
>    static assert(i < 3);
> }
> 
> How does it know whether the static break should be "executed" at compile-time if it hasn't evaluated the if-statement? The code would have to have no runtime branches to make sure that static break can be evaluated at compile-time.
[...]

Static foreach does not (and should not!) evaluate a runtime branch, because this is before CTFE even happens. CTFE cannot happen until the static foreach has been fully unrolled, so it doesn't even make sense to talk about evaluating the if-statement at this point. For your example to make sense, you'd have to use static if, then the break would be possible, and non-problematic.

Of course, that still doesn't solve the problem of what static break is supposed to do from inside a runtime branch. I'm tempted to say that static break should mean "delete all subsequent nodes from the AST that follows this node in depth-first traversal order", so your example above would be transformed into:

	if (0 == 3) {}
	// all subsequent iterations deleted

because the static break is unconditionally compiled (it has nothing to do with the runtime branch).  You'd have to use static if to make it conditionally-compiled and thus not instantly aborting the loop.

Such semantics would be logically consistent, but unfortunately rather counterintuitive at first glance.


T

-- 
If the comments and the code disagree, it's likely that *both* are wrong. -- Christopher
January 10, 2018
On 09.01.2018 22:04, H. S. Teoh wrote:
> 	if (0 == 3) {}
> 	// all subsequent iterations deleted
> 
> because the static break is unconditionally compiled (it has nothing to
> do with the runtime branch).  You'd have to use static if to make it
> conditionally-compiled and thus not instantly aborting the loop.
> 
> Such semantics would be logically consistent, but unfortunately rather
> counterintuitive at first glance.

I think "if (0 == 3) { static break; }" should be a compile-time error.