std.algorithm.find是否需要引用范围元素?

时间:2015-02-03 17:15:09

标签: d phobos

我一直致力于基于类的有限随机访问范围。在对它进行一些测试时:

auto myRange = /* construct my range */
static assert (isRandomAccessRange!(typeof(myRange))); // 
static assert (!isInfinite!(typeof(myRange)));         // both pass 
auto preamble = myRange[0..128];
assert( all!"a == 0"(preamble)); // check for all zeros

我在GDC 4.9.2中遇到了这个编译错误,关于上面代码片段中的最后一行:“algorithm.d | 4838 |错误:foreach:无法生成e ref”

错误指向std.algorithm.find中的这段代码(find_if变体,带有范围和谓词),它确实引用了foreach的每个元素:

InputRange find(alias pred, InputRange)(InputRange haystack)
if (isInputRange!InputRange)
{
    alias R = InputRange;
    alias predFun = unaryFun!pred;
    static if (isNarrowString!R)
    {
        ...
    }
    else static if (!isInfinite!R && hasSlicing!R && is(typeof(haystack[cast(size_t)0 .. $])))
    {
        size_t i = 0;
        foreach (ref e; haystack) // <-- needs a ref
        {
            if (predFun(e))
                return haystack[i .. $];
            ++i;
        }
        return haystack[$ .. $];
    }
    else
    {
       ...
    }
}

这很可能是因为我提供了opApply的实现,它没有提供ref参数(该类也没有为任何其他成员函数提供ref返回类型)

int opApply(int delegate(E) f) {...}
int opApply(int delegate(size_t,E) f) {...}

我可以改变这一点,但真正困扰我的是,现在范围类符合函数的前提条件,并且foreach迭代仍然应该与它们一起工作。引自文档:

  

可以使用范围对结构和类对象进行迭代。对于foreach,这意味着必须定义以下属性和方法:

     

属性:

     
      如果没有更多元素,
  • .empty将返回true
  •   
  • .front返回范围最左边的元素
  •   
     

方法:

     
      
  • .popFront()将范围的左边缘向右移动
  •   

提供了所有这些(否则它不是随机访问范围),所以它应该使用它们。相反,它可能正在寻找下面描述的替代迭代方法:

  

如果聚合表达式是结构或类对象,且范围属性不存在,则foreach由特殊opApply成员函数定义,并且foreach_reverse行为已定义通过特殊的opApplyReverse成员函数。这些函数的类型为:

     

int opApply(int delegate(ref Type [, ...]) dg);

在我的解释中,不应该寻找。

同样引用std.algorithm.all,它似乎不需要迭代引用:

  

bool all(Range)(Range range) if (isInputRange!Range && is(typeof(unaryFun!pred(range.front))));

     

当且仅当if时返回true   在输入范围范围内找到的所有值v都满足谓词   预计值。对pred进行(最多)Ο(range.length)评估。

这是Phobos库中的一个错误,std.algorithm.find应该首先按值迭代吗?或者有什么我错过了吗?

1 个答案:

答案 0 :(得分:1)

在一个应该是一个范围的对象上声明opApply甚至没有意义,因为如果它是一个范围,那么基于范围的函数将用于foreach,而不是opApply。当然,如果在范围类型而不是opApplyfrontpopFront上调用empty,那么这就是编译器错误。从它的声音来看,编译器错误地选择opApply,因为opApply使用ref,而front没有。但是,只要未声明frontref只有foreach ref使用opApplyref才能正常工作。因此,opApply不是问题,因为当编译器看到opApplyreffront没有opApply时错误地使用了opApply这一事实“T

所以,编译器需要修复,但这可能永远不会被捕获,因为在你正在做的范围类型上声明save是没有意义的。因此,我认为您的代码需要更改为不在范围类型上声明haystack。然后你甚至不会遇到这个特殊的错误。

话虽如此,Phobos 中的代码对于作为引用类型(如类)的范围来说是错误,因为它无法在opApply上调用opApply它迭代它。结果是原始范围变异以引用正在搜索的点,而返回的点指向远离正确点的元素来自干草堆的前面。因此,即使您停止声明opApply和/或编译器错误得到修复,也需要修复std.algorithm.find,以便在您使用范围的引用类型时代码开始工作。 / p>

修改

好。那不太对劲。在与一些编译器开发者讨论它时我得到了纠正。过去,范围函数比opApply更受青睐,这就是规范所说的,但在某些时候,它被改变,因此foreach比范围函数更受青睐,因此范围类型可以使用opApplyopApply进行迭代,如果它对它更有效(尽管这显然会引入范围函数和opApply没有相同行为的风险,这可能会导致一些真正的令人讨厌的错误)。因此,规范与编译器的当前行为不匹配,并且 应该为你在范围类型上声明ref(尽管我仍然建议不要使用它,除非你'从中获得明确的性能提升)。

话虽如此,你在这里收到错误的事实仍然是编译错误。由于ref不使用opApply,因此它不适用于opApply循环变量,而范围函数则适用,因此在这种情况下编译器应该调用范围函数,显然不是。无论哪种方式,这都没有被捕获,因为几乎没有人在范围上使用{{1}},因为这样做的唯一原因是如果这样做会有性能提升,而且我确信这个事实是spec仍然表示范围函数比{{1}}更受欢迎,这使得尝试它的人数比其他人少。