-
-
Notifications
You must be signed in to change notification settings - Fork 378
Specify opApply
as an alias to a function template instance behavior
#3859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Bolpat
wants to merge
10
commits into
dlang:master
Choose a base branch
from
Bolpat:OpApplyImplPattern
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
a4ff66e
Fix Bugzilla Issues 23666, 17953, and 24633
132cad9
Apply suggestions from code review
Bolpat eb2e578
Address code review: Remove `OpApplyParameter`
c7bee13
Fix phrasing, layout, and highlighting
e61e477
Revise initial example; use Tree as an example more
698bbda
Fix Error with ---
31976bb
Fix minor phrasing errors
58e1e5b
Code review: Make example runnable
bc0fe55
Make some part-sentences non-paragraphs.
85c520d
Rephrase subsection heading
Bolpat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -698,88 +698,100 @@ foreach (string s, double d; aa) | |
|
||
$(H3 $(LNAME2 foreach_over_struct_and_classes, Foreach over Structs and Classes with `opApply`)) | ||
|
||
$(P If the aggregate expression is a struct or class object, | ||
the $(D foreach) is defined by the | ||
special $(LEGACY_LNAME2 opApply, op-apply, $(D opApply)) member function, and the | ||
`foreach_reverse` behavior is defined by the special | ||
$(P If the aggregate expression is a `struct` or `class` object, | ||
iteration with $(D foreach) can be defined by implementing the | ||
$(LEGACY_LNAME2 opApply, op-apply, $(D opApply)) member function, and the | ||
`foreach_reverse` behavior is defined by the | ||
$(LEGACY_LNAME2 opApplyReverse, op-apply-reverse, $(D opApplyReverse)) member function. | ||
These functions must each have the signature below: | ||
) | ||
|
||
$(GRAMMAR | ||
$(GNAME OpApplyDeclaration): | ||
`int opApply` `(` `scope` `int delegate` `(` $(I OpApplyParameters) `)` `dg` `)` `;` | ||
`int opApply` `(` `scope` `int delegate` `(` $(I OpApplyParameters) `)` `body` `)` `;` | ||
|
||
$(GNAME OpApplyParameters): | ||
*OpApplyParameter* | ||
*OpApplyParameter*, *OpApplyParameters* | ||
$(GLINK ParameterDeclaration) | ||
$(GLINK ParameterDeclaration), *OpApplyParameters* | ||
|
||
$(GNAME OpApplyParameter): | ||
$(GLINK ForeachTypeAttributes)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator) | ||
$(GLINK ParameterStorageClass)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator) | ||
) | ||
|
||
$(P where each $(I OpApplyParameter) of `dg` must match a $(GLINK ForeachType) | ||
in a $(GLINK ForeachStatement), | ||
otherwise the *ForeachStatement* will cause an error.) | ||
Comment on lines
-721
to
-723
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would keep this paragraph too (though it would need to use ParameterDeclaration if you reinstate that change). |
||
|
||
$(P Any *ForeachTypeAttribute* cannot be `enum`.) | ||
|
||
$(PANEL | ||
To support a `ref` iteration variable, the delegate must take a `ref` parameter: | ||
|
||
$(SPEC_RUNNABLE_EXAMPLE_COMPILE | ||
--- | ||
struct S | ||
{ | ||
int opApply(scope int delegate(ref uint n) dg); | ||
uint n; | ||
int opApply(scope int delegate(ref uint) body) => body(n); | ||
} | ||
void f(S s) | ||
void main() | ||
{ | ||
S s; | ||
foreach (ref uint i; s) | ||
i++; | ||
{ | ||
i++; // effectively s.n++ | ||
} | ||
foreach (i; s) | ||
{ | ||
static assert(is(typeof(i) == uint)); | ||
assert(i == 1); | ||
} | ||
} | ||
--- | ||
) | ||
Above, `opApply` is still matched when `i` is not `ref`, so by using | ||
a `ref` delegate parameter both forms are supported. | ||
) | ||
Above, `opApply` is matched both when `i` is and is not `ref`, so by using | ||
a `ref` delegate parameter, both forms are supported. | ||
|
||
$(P There can be multiple $(D opApply) and $(D opApplyReverse) functions - | ||
one is selected | ||
by matching each parameter of `dg` to each $(I ForeachType) | ||
declared in the $(I ForeachStatement).) | ||
Stating the type of the variable like in the first loop is optional, | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
as is showcased in the second loop, where the type is inferred. | ||
) | ||
|
||
$(P The body of the apply | ||
function iterates over the elements it aggregates, passing each one | ||
in successive calls to the `dg` delegate. The delegate return value | ||
determines whether to interrupt iteration:) | ||
$(P The apply | ||
function iterates over the elements it aggregates by passing each one | ||
in successive calls to the `body` delegate. The delegate return value | ||
of the `body` delegate | ||
determines how to proceed iteration:) | ||
|
||
$(UL | ||
$(LI If the result is nonzero, apply must cease | ||
$(LI If the result is nonzero, the apply function must cease | ||
iterating and return that value.) | ||
$(LI If the result is 0, then iteration should continue. | ||
$(LI If the result is `0`, then iteration should continue. | ||
If there are no more elements to iterate, | ||
apply must return 0.) | ||
the apply function must return `0`.) | ||
) | ||
|
||
$(P The result of calling the delegate will be nonzero if the *ForeachStatement* | ||
$(P The result of the `body` delegate will be nonzero if the *ForeachStatement* | ||
body executes a matching $(GLINK BreakStatement), $(GLINK ReturnStatement), or | ||
$(GLINK GotoStatement) whose matching label is outside the *ForeachStatement*. | ||
) | ||
|
||
$(P For example, consider a class that is a container for two elements:) | ||
$(P The $(D opApply) and $(D opApplyReverse) member functions can be overloaded. | ||
Selection works similar to overload resolution by | ||
comparing the number of `foreach` variables and number of parameters of `body` and, | ||
if more than one overload remains unique overload, | ||
matching the parameter types of `body` and each $(I ForeachType) | ||
declared in the $(I ForeachStatement) when given.) | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$(BEST_PRACTICE Overload apply functions only with delegates differing in number of parameters | ||
to enable type inference for `foreach` variables. | ||
) | ||
|
||
$(P For example, consider a class that is a container for some elements:) | ||
|
||
$(SPEC_RUNNABLE_EXAMPLE_RUN | ||
-------------- | ||
class Foo | ||
{ | ||
uint[] array; | ||
|
||
int opApply(scope int delegate(ref uint) dg) | ||
int opApply(scope int delegate(ref uint) body) | ||
{ | ||
foreach (e; array) | ||
{ | ||
int result = dg(e); | ||
int result = body(e); | ||
if (result) | ||
return result; | ||
} | ||
|
@@ -811,57 +823,159 @@ $(CONSOLE | |
73 | ||
82 | ||
) | ||
$(PANEL | ||
The `scope` storage class on the $(D dg) parameter means that the delegate does | ||
not escape the scope of the $(D opApply) function (an example would be assigning $(D dg) to a | ||
global variable). If it cannot be statically guaranteed that $(D dg) does not escape, a closure may | ||
be allocated for it on the heap instead of the stack. | ||
|
||
$(BEST_PRACTICE Annotate delegate parameters to `opApply` functions with `scope` when possible.) | ||
|
||
$(PANEL | ||
The `scope` storage class on the `body` parameter means that the delegate does | ||
not escape the scope of the apply function (an example would be assigning`body` to a | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
global variable). If it cannot be statically guaranteed that `body` does not escape, a closure may | ||
be allocated for it on the heap instead of the stack. | ||
) | ||
|
||
$(P $(B Important:) If $(D opApply) catches any exceptions, ensure that those | ||
exceptions did not originate from the delegate passed to $(D opApply). The user would expect | ||
$(P $(B Important:) If apply functions catch any exceptions, ensure that those | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
exceptions did not originate from the delegate. The user would expect | ||
exceptions thrown from a `foreach` body to both terminate the loop, and propagate outside | ||
the `foreach` body. | ||
) | ||
|
||
$(H4 $(LNAME2 template-op-apply, Template `opApply`)) | ||
|
||
$(P $(D opApply) can also be a templated function, | ||
which will infer the types of parameters based on the $(I ForeachStatement). | ||
For example:) | ||
$(P `opApply` and `opApplyReverse` can also be a function templates, | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
which can optionally infer the types of parameters based on the $(I ForeachStatement). | ||
) | ||
|
||
$(P $(B Note:) An apply function template cannot infer `foreach` variable types.) | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$(SPEC_RUNNABLE_EXAMPLE_RUN | ||
-------------- | ||
struct S | ||
{ | ||
import std.traits : ParameterTypeTuple; // introspection template | ||
import std.stdio; | ||
|
||
int opApply(Dg)(scope Dg dg) | ||
if (ParameterTypeTuple!Dg.length == 2) // foreach with 2 parameters | ||
int opApply(Body)(scope Body body) | ||
{ | ||
writeln(2); | ||
return 0; | ||
} | ||
|
||
int opApply(Dg)(scope Dg dg) | ||
if (ParameterTypeTuple!Dg.length == 3) // foreach with 3 parameters | ||
{ | ||
writeln(3); | ||
pragma(msg, Body); | ||
return 0; | ||
} | ||
} | ||
|
||
void main() | ||
{ | ||
foreach (int a, int b; S()) { } // calls first opApply function | ||
foreach (int a, int b, float c; S()) { } // calls second opApply function | ||
foreach (int a, int b; S()) { } // int delegate(ref int, ref int) pure nothrow @nogc @safe | ||
foreach (bool b, string s; S()) { } // int delegate(ref bool, ref string) pure nothrow @nogc @safe | ||
//foreach (x; S()) { } // Error: cannot infer type for `foreach` variable `x`, perhaps set it explicitly | ||
} | ||
-------------- | ||
) | ||
|
||
$(H4 $(LNAME2 template-instance-op-apply, `opApply` as an alias to a explicit function template instance)) | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$(P `opApply` and `opApplyReverse` can be aliases to an appropriate function or function template. | ||
However, special treatment is given to apply functions that are aliases of an appropriate function template instance.) | ||
|
||
$(P In that case, the function template instance is used for overload selection and `foreach` variable type inference, | ||
but the template is instantiated again with the actual delegate type of the `foreach` body. | ||
This way, apply functions can infer attributes depending on the attributes of `body` delegate.) | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$(SPEC_RUNNABLE_EXAMPLE_RUN | ||
-------------- | ||
struct A | ||
{ | ||
int opApply(scope int delegate(long) body) => body(42); | ||
} | ||
struct B | ||
{ | ||
int opApply(Body)(scope Body body) => body(42); | ||
} | ||
struct C | ||
{ | ||
int opApplyImpl(Body)(scope Body body) => body(42); | ||
alias opApply = opApplyImpl!(int delegate(long)); | ||
} | ||
void main() @nogc nothrow pure @safe | ||
{ | ||
// Error: `@nogc` function `D main` cannot call non-@nogc function `onlineapp.A.opApply` | ||
// Error: `pure` function `D main` cannot call impure function `onlineapp.A.opApply` | ||
// Error: `@safe` function `D main` cannot call `@system` function `onlineapp.A.opApply` | ||
// Error: function `onlineapp.A.opApply` is not `nothrow` | ||
static assert(!__traits(compiles, () @safe { | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
foreach (x; A()) { } | ||
})); | ||
|
||
// Error: cannot infer type for `foreach` variable `x`, perhaps set it explicitly | ||
static assert(!__traits(compiles, { | ||
foreach (x; B()) { } | ||
})); | ||
|
||
// Good: | ||
foreach (x; C()) | ||
{ | ||
static assert(is(typeof(x) == long)); | ||
assert(x == 42); | ||
} | ||
} | ||
-------------- | ||
) | ||
|
||
$(P The `opApplyImpl` pattern is generally preferable to | ||
overloading many apply functions with all possible combinations of attributes.) | ||
|
||
$(P Multiple apply function aliases can exist, and selection and `foreach` variable type inference work:) | ||
|
||
-------------- | ||
class Tree(T) | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private T label; | ||
private Tree[] children; | ||
|
||
this(T label, Tree[] children = null) | ||
{ | ||
this.label = label; | ||
this.children = children; | ||
} | ||
|
||
alias opApply = opApplyImpl!(int delegate(ref T label)); | ||
alias opApply = opApplyImpl!(int delegate(size_t depth, ref T label)); | ||
alias opApply = opApplyImpl!(int delegate(size_t depth, bool isLastChild, ref T label)); | ||
|
||
int opApplyImpl(Body)(scope Body body) => opApplyImpl2(0, true, body); | ||
int opApplyImpl2(Body)(size_t depth, bool lastChild, scope Body body) | ||
{ | ||
import std.meta : AliasSeq; | ||
static if (is(Body : int delegate(size_t, bool, ref T))) | ||
alias args = AliasSeq!(depth, lastChild, label); | ||
else static if (is(Body : int delegate(size_t, ref T))) | ||
alias args = AliasSeq!(depth, label); | ||
else | ||
alias args = label; | ||
if (auto result = body(args)) return result; | ||
foreach (i, child; children) | ||
{ | ||
if (auto result = child.opApplyImpl2!Body(depth + 1, i + 1 == children.length, body)) | ||
return result; | ||
} | ||
return 0; | ||
} | ||
} | ||
|
||
void printTree(Tree)(Tree tree) | ||
{ | ||
// Selects the unique one with 2 parameters. | ||
// Infers types: size_t and whatever label type the tree has. | ||
foreach (depth, ref label; tree) | ||
{ | ||
import std.stdio; | ||
foreach (_; 0 .. depth) write(" "); | ||
writeln(label); | ||
} | ||
} | ||
|
||
void main() @safe | ||
{ | ||
alias T = Tree!int; | ||
printTree(new T(0, [new T(1, [new T(2), new T(3)]), new T(4, [new T(5)])])); | ||
} | ||
-------------- | ||
|
||
$(H3 $(LEGACY_LNAME2 foreach_with_ranges, foreach-with-ranges, Foreach over Structs and Classes with Ranges)) | ||
|
||
$(P If the aggregate expression is a struct or class object, but the | ||
|
@@ -1028,16 +1142,16 @@ $(H3 $(LNAME2 foreach_over_delegates, Foreach over Delegates)) | |
$(SPEC_RUNNABLE_EXAMPLE_RUN | ||
-------------- | ||
// Custom loop implementation, that iterates over powers of 2 with | ||
// alternating sign. The foreach loop body is passed in dg. | ||
int myLoop(scope int delegate(int) dg) | ||
// alternating sign. The foreach loop body is passed in `body`. | ||
int myLoop(scope int delegate(int) body) | ||
{ | ||
for (int z = 1; z < 128; z *= -2) | ||
{ | ||
auto ret = dg(z); | ||
auto result = body(z); | ||
|
||
// If the loop body contains a break, ret will be non-zero. | ||
if (ret != 0) | ||
return ret; | ||
// If the loop body contains a break, result will be non-zero. | ||
if (result != 0) | ||
return result; | ||
} | ||
return 0; | ||
} | ||
|
@@ -1583,7 +1697,7 @@ foreach (item; list) | |
$(P Any intervening $(RELATIVE_LINK2 try-statement, `finally`) clauses are executed, | ||
and any intervening synchronization objects are released.) | ||
|
||
$(P $(D Note:) If a `finally` clause executes a `throw` out of the finally | ||
$(P $(B Note:) If a `finally` clause executes a `throw` out of the finally | ||
Bolpat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
clause, the continue target is never reached.) | ||
|
||
$(H2 $(LEGACY_LNAME2 BreakStatement, break-statement, Break Statement)) | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.