每当我尝试在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)
如果你没有,你应该阅读本教程.
将要消耗的范围取决于其类型.如果它是一个输入范围而不是前向范围(例如,如果它是某种类型的输入流 - 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功能在参考类型前向范围内无法正常工作,请报告).因此,引用类型转发范围并不总是按预期工作.
抱歉,我无法将其放入评论中: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没有为例如文件和套接字定义。