C++ unexpected value type of range-v3 partial_sum view

438 views Asked by At

Consider the following minimal example:

#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<<" ";
    }
}

This outputs

6 3 2 3 4 5 

I would have expected to see double numbers here, but the result are obvisouly integers. This is in contrast to the behavior of view::transform.

The reason for this is because in the implementation, the running-sum value has a type that corresponds to the source range:

semiregular_t<range_value_type_t<Rng>> sum_;

Is this intended or a bug?


Discussion: I see the trouble one is running into when trying to get a valid return type, as as the transformation function is using both the source range and the result range as parameters and produces a return type. The next application uses the source-range-type and this return type to produce another (possibly different) return type, and so on.

By this, in principle, one is repeatedly chaining the source-value-type with the result types of the transformation function. This repeated iteration yields something usable only if the result type "converges" to a specific type to which all other intermediate results can be converted to (in the example above, this type is double, which is obtained already after the first call of the transformation function).

With this observation one could propose a workaround: apply the binary transformation function a given number of times and the use the common_type as value type of the resulting range (if one finds a convergence, prematurely stop). In the simplest case, the number of iterations is just one. If this iteration does not lead to something reasonable, one can still resort to the source-value-type (or a compiler error).

To make it clear, here is the application for the example above:

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

After the third iteration the pattern converges, so stop and choose the common-type double as the value_type of the returned range.

I'm not sure whether this approach is completely valid in all theoretical circumstances, but at least it gives a double in the first example -- which I guess is what everyone is strongly expecting.

1

There are 1 answers

1
Casey On BEST ANSWER

ranges::view::partial_sum by design mirrors the semantics of std::partial_sum. If you run:

#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<<" ";
    }
}

You should get exactly the same output as from the program in the OP. Like many range-v3 views, this view's job is to compute the same sequence of results as computed by a standard algorithm, but do so lazily.

std::partial_sum is specified to operate on an accumulator whose type is the same is the value type of the input range. [partial.sum]/2 says:

Effects: For a non-empty range, the function creates an accumulator acc whose type is InputIterator's value type, initializes it with *first, and assigns the result to *result. For every iterator i in [first + 1, last) in order, acc is then modified by acc = acc + *i or acc = binary_­op(acc, *i) and the result is assigned to *(result + (i - first)).

To behave equivalently, ranges::view::partial_sum also uses an accumulator whose type is the value type of the input range.

In the case of the OP, you can achieve the desired result by using double as the type of the input range. With range-v3 this is easy to do on-the-fly by composing with 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<<" ";
    }
}

which produces the desired output:

6 3.2 3.06 3.718 4.6154 5.58462