C++中range-v3库的partial_sum视图出现意外的值类型

4
考虑以下最简示例:
#include <range/v3/all.hpp>
#include <iostream>

namespace rng = ranges::v3;

int main() 
{
    std::vector<int> v { 6, 2, 3, 4, 5, 6 };
    auto f    = [](auto a, auto b) { return a*0.3 + b*0.7;};
    auto rng  = v | rng::view::partial_sum(f);

    for(auto i : rng)
    {
        std::cout<<i<<" ";
    }
}

这将输出

6 3 2 3 4 5 

我本来期望这里出现的是双倍数,但结果显然是整数。这与view::transform的行为相反。

原因在于,在实现中,运行总和的值的类型对应于源范围:

semiregular_t<range_value_type_t<Rng>> sum_;

这是故意的还是一个错误?


讨论:当试图获得有效的返回类型时,我看到了一个人在尝试使用源范围和结果范围作为参数,并生成返回类型的转换函数时遇到的问题。下一个应用程序使用源范围类型和此返回类型来产生另一种(可能不同的)返回类型,以此类推。

因此,原则上,一个人正在重复地将源值类型与转换函数的结果类型链接起来。只有当结果类型“收敛”到特定类型时,才能使用这种重复迭代的东西,所有其他中间结果都可以转换为该类型(在上面的示例中,该类型已在转换函数的第一次调用后获得,即double)。

通过这个观察,我们可以提出一个解决方法:多次应用二进制转换函数,然后使用common_type作为结果范围的值类型(如果找到收敛,则提前停止)。在最简单的情况下,迭代次数只有一次。如果这次迭代没有导致合理的结果,仍然可以使用源值类型(或编译器错误)。

为了清楚起见,这里是上面示例的应用:

First iteration : f(int,int)    -> yields "double"
Second iteration: f(int,double) -> yields "double"
Third iteration : f(int,double) -> yields "double"

在第三次迭代后,模式会收敛,因此停止并选择常见类型double作为返回范围的value_type。
我不确定这种方法在所有理论情况下是否完全有效,但至少它在第一个例子中给出了一个双精度浮点数--我想这正是每个人强烈期望的。

你为什么不能发布一个完整的、可编译的示例呢? - user2100815
@NeilButterworth:我的代码片段和SO上的任何其他有效代码一样可编译 - davidhigh
你在这里发布的代码无法编译。为什么你不能添加最少量的代码来使其能够编译呢? - user2100815
@NeilButterworth:抱歉,我不明白你的意思。哪段代码?第一个示例可以编译,可以看到我在评论中的链接。另一个是伪代码。 - davidhigh
这就是它应该运作的方式。鉴于你的代码,我怎么知道你是否已经包含了正确的头文件、声明“rng”的不同的头文件或其他很多东西。你通过链接发布的代码澄清了所有这些,而且发布它所需要的时间比我们现在争论的时间更少。 - user2100815
@davidhigh,你在链接中的内容可以编译通过,但它并不是你在问题中提出的内容。这里的问题是上帝,而链接只是一个单细胞生物。 - user4581301
1个回答

5

ranges::view::partial_sum 的设计与 std::partial_sum 的语义相似。如果您运行:

#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>

int main() 
{
    std::vector<int> v { 6, 2, 3, 4, 5, 6 };
    auto f = [](auto a, auto b) { return a*0.3 + b*0.7; };
    std::vector<double> rng;
    std::partial_sum(v.begin(), v.end(), std::back_inserter(rng), f);

    for(auto i : rng)
    {
        std::cout<<i<<" ";
    }
}

你应该得到与原始程序完全相同的输出。就像许多range-v3视图一样,这个视图的工作是惰性地计算与标准算法计算出的相同结果序列。 std::partial_sum 指定要操作一个类型与输入范围的值类型相同的累加器. [partial.sum]/2 说:

效果:对于非空范围,函数创建一个累加器 acc,其类型为 InputIterator 的值类型,用 *first 初始化它,并将结果赋值给 *result。 对于顺序中的每个迭代器 i[first + 1,last) 中,然后通过 acc = acc + *iacc = binary_­op(acc, *i) 修改 acc ,并将结果分配给 *(result + (i - first))

为了达到等效的行为,ranges::view::partial_sum 也使用一个累加器,其类型是输入范围的值类型。
在此示例中,您可以通过使用 double 作为输入范围的类型来实现所需的结果。使用 range-v3,可以通过与 ranges::view::transform(ranges::convert_to<double>{}) 组合来轻松完成:
#include <range/v3/all.hpp>
#include <iostream>

namespace rng = ranges::v3;

int main() 
{
    std::vector<int> v { 6, 2, 3, 4, 5, 6 };
    auto f    = [](auto a, auto b) { return a*0.3 + b*0.7;};
    auto rng  = v | rng::view::transform(rng::convert_to<double>{}) |
        rng::view::partial_sum(f);

    for(auto i : rng)
    {
        std::cout<<i<<" ";
    }
}

生成所需的输出

6 3.2 3.06 3.718 4.6154 5.58462

委员会尚未确定这是否是正确的行为。我认为我们在这里可以自由创新。https://cplusplus.github.io/LWG/issue539 - Eric Niebler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接