每当我尝试在D中使用范围时,我都会惨败。
在 D 中使用范围的正确方法是什么?(请参阅内联评论以了解我的困惑。
void print(R)(/* ref? auto ref? neither? */ R r)
{
foreach (x; r)
{
writeln(x);
}
// Million $$$ question:
//
// Will I get back the same things as last time?
// Do I have to check for this every time?
foreach (x; r)
{
writeln(x);
}
}
void test2(alias F, R)(/* ref/auto ref? */ R items)
{
// Will it consume items?
// _Should_ it consume items?
// Will the caller be affected? How do I know?
// Am I supposed to?
F(items);
}
如果没有,您可能应该阅读有关范围的本教程。
何时会消耗和不使用范围取决于其类型。如果它是一个输入范围而不是前向范围(例如,如果它是某种输入流 - std.stdio.byLine
就是一个例子),那么以任何方式迭代它形状或形式都会消耗它。
//Will consume
auto result = find(inRange, needle);
//Will consume
foreach(e; inRange) {}
如果它是一个向前范围并且它是一个引用类型,那么只要你迭代它,它就会被使用,但你可以调用save
来获取它的副本,并且使用副本不会消耗原始副本(使用原始副本也不会消耗副本)。
//Will consume
auto result = find(refRange, needle);
//Will consume
foreach(e; refRange) {}
//Won't consume
auto result = find(refRange.save, needle);
//Won't consume
foreach(e; refRange.save) {}
事情变得更有趣的地方是前向范围,它们是值类型(或数组)。它们在save
方面的作用与任何正向范围相同,但它们的不同之处在于简单地将它们传递给函数或在隐式save
foreach
中使用它们。
//Won't consume
auto result = find(valRange, needle);
//Won't consume
foreach(e; valRange) {}
//Won't consume
auto result = find(valRange.save, needle);
//Won't consume
foreach(e; valRange.save) {}
因此,如果您正在处理的输入范围不是前向范围,则无论如何都会消耗它。如果你正在处理一个前向范围,如果你想保证它不会被消耗,你需要调用save
- 否则它是否被消耗取决于它的类型。
关于ref
,如果你声明一个基于范围的函数通过ref
获取它的参数,那么它不会被复制,所以传入的范围是否是引用类型并不重要,但这确实意味着你不能传递一个右值,这真的很烦人,所以你可能不应该在范围参数上使用ref
,除非你实际上需要它总是改变原始参数(例如 std.range.popFrontN
需要ref
,因为它显式地改变了原始文件,而不是可能在副本上运行)。
至于使用前向范围调用基于范围的函数,值类型范围最有可能正常工作,因为代码通常是使用值类型范围编写和测试的,并不总是使用引用类型进行正确测试。不幸的是,这包括火卫一的函数(尽管这将被修复;它还没有在所有情况下都经过适当的测试 - 如果你遇到任何Phobos函数在引用类型前向范围内无法正常工作的情况,请报告它)。因此,引用类型向前范围并不总是按预期工作。
抱歉,我无法将其放入评论:D。考虑是否以这种方式定义范围:
interface Range {
void doForeach(void delegate() myDel);
}
你的函数看起来像这样:
void myFunc(Range r) {
doForeach(() {
//blah
});
}
当你重新分配 r 时,你不会期望发生任何奇怪的事情,你也不会期望以便能够修改调用方的范围。我认为问题在于您希望模板函数能够解释范围类型的所有变化,同时仍然利用专业化。那行不通。您可以将合同应用于模板以利用专用化,或仅使用常规功能。这有帮助吗?
编辑(我们在评论中一直在谈论的内容):
void funcThatDoesntRuinYourRanges(R)(R r)
if (isForwardRange(r)) {
//do some stuff
}
编辑 2 标准范围 看起来isForwardRange
只是检查是否定义了save
,而save
只是一个原始语,可以创建该范围的某种未链接副本。文档指定未为文件和套接字等定义save
。
它的短者;范围被消耗。这是您应该期待和计划的。
foreach 上的 ref 在这方面没有任何作用,它只与范围返回的值有关。
长范围会被消耗,但可能会被复制。您需要查看文档以决定将会发生什么。值类型被复制,因此范围在传递给函数时可能不会被修改,但是如果范围作为结构作为数据流作为引用,则不能依赖,例如 FILE。当然,ref 函数参数会增加混乱。
假设您的print
函数如下所示:
void print(R)(R r) {
foreach (x; r) {
writeln(x);
}
}
在这里,r
使用引用语义传递到函数中,使用泛型类型 R
:所以你不需要这里ref
(auto
会给出编译错误)。否则,这将逐项打印r
的内容。(我似乎记得有一种方法可以将泛型类型限制为范围的泛型类型,因为范围具有某些属性,但我忘记了细节!
无论如何:
auto myRange = [1, 2, 3];
print(myRange);
print(myRange);
。将输出:
1
2
3
1
2
3
如果将函数更改为(假设x++
对您的范围有意义):
void print(R)(R r) {
foreach (x; r) {
x++;
writeln(x);
}
}
。然后每个元素在打印之前都会增加,但这使用的是复制语义。也就是说,myRange
中的原始值不会更改,因此输出将为:
2
3
4
2
3
4
但是,如果将函数更改为:
void print(R)(R r) {
foreach (ref x; r) {
x++;
writeln(x);
}
}
。然后将x
恢复为引用语义,引用myRange
的原始元素。因此,输出现在将是:
2
3
4
3
4
5