你如何在D中使用范围?

Meh*_*dad 7 d range phobos

每当我尝试在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);
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*vis 8

如果你没有,你应该阅读本教程.

将要消耗的范围取决于其类型.如果它是一个输入范围而不是前向范围(例如,如果它是某种类型的输入流 - std.stdio.byLine将是其中的一个示例),那么以任何形式或形式迭代它将消耗它.

//Will consume
auto result = find(inRange, needle);

//Will consume
foreach(e; inRange) {}
Run Code Online (Sandbox Code Playgroud)

如果它是一个前向范围并且它是一个引用类型,那么每当你遍历它时它就会消耗掉,但你可以调用它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) {}
Run Code Online (Sandbox Code Playgroud)

事情变得更有趣的是前向范围,即值类型(或数组).它们的行为与任何前向范围相同save,但它们的不同之处在于它们只是简单地将它们传递给函数或者foreach隐含地使用save它们.

//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) {}
Run Code Online (Sandbox Code Playgroud)

因此,如果您正在处理的输入范围不是前向范围,则无论如何都会消耗它.如果你正在处理一个前进范围,你需要打电话,save如果你想要保证它不被消耗 - 否则它是否消耗取决于它的类型.

关于ref,如果你声明一个基于范围的函数来获取它的参数ref,那么它将不会被复制,因此传入的范围是否是引用类型无关紧要,但它确实意味着你不能传递一个非常烦人的右值,所以你可能不应该使用ref范围参数,除非你真的需要它总是改变原始的(例如std.range.popFrontN,ref因为它明确地改变原始而不是可能在复制).

对于使用前向范围调用基于范围的函数,值类型范围最有可能正常工作,因为代码经常使用值类型范围编写和测试,并且不总是使用引用类型进行适当测试.不幸的是,这包括Phobos的功能(虽然它将被修复;它在所有情况下都没有经过适当的测试 - 如果遇到任何情况下Phobos功能在参考类型前向范围内无法正常工作,请报告).因此,引用类型转发范围并不总是按预期工作.


Tim*_*Tim 4

抱歉,我无法将其放入评论中:D。考虑 Range 是否以这种方式定义:

interface Range {
    void doForeach(void delegate() myDel);
}
Run Code Online (Sandbox Code Playgroud)

你的函数看起来像这样:

void myFunc(Range r) {
    doForeach(() {
        //blah
    });
}
Run Code Online (Sandbox Code Playgroud)

当您重新分配 r 时,您不会期望发生任何奇怪的事情,也不会期望能够修改调用者的 Range。我认为问题在于您期望模板函数能够解释范围类型的所有变化,同时仍然利用专业化。那是行不通的。您可以将合同应用于模板以利用专业化,或仅使用常规功能。这有帮助吗?

编辑(我们在评论中讨论的内容):

void funcThatDoesntRuinYourRanges(R)(R r)
if (isForwardRange(r)) {
    //do some stuff
}
Run Code Online (Sandbox Code Playgroud)

编辑 2 std.range它看起来isForwardRange只是检查是否save已定义,并且save只是一个原语,它制作了范围的某种未链接副本。文档指定save没有为例如文件和套接字定义。