如何使用 D 中的范围



每当我尝试在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 :所以你不需要这里refauto会给出编译错误)。否则,这将逐项打印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

相关内容

  • 没有找到相关文章

最新更新