4

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.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • Why could you not have posted a complete, compilable example? –  Sep 09 '17 at 23:01
  • @NeilButterworth: my code snippet is as [compilable](http://coliru.stacked-crooked.com/a/16b2fab2750ef15f) as any other valid code here on SO. – davidhigh Sep 09 '17 at 23:10
  • The code you posted here is not compilable. Why could you have not added the minimal code to make it so? –  Sep 09 '17 at 23:11
  • @NeilButterworth: sorry, I don't get your point. Which code? The first example compiles, see the link in my comment before. The other one is pseudo-code. – davidhigh Sep 09 '17 at 23:14
  • It's the way it is supposed to work. Given your code, how do I know you have included the correct header files, different header files that declare `rng`, or a lot of other things. The code you posted by a link clarified all that, and would have taken less time to post here than this argument has. –  Sep 09 '17 at 23:16
  • @davidhigh what you have in the link compiles, but it is not what you put in the question. Here the question is god. The link is... maybe an amoeba. – user4581301 Sep 09 '17 at 23:19

1 Answers1

5

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
Casey
  • 41,449
  • 7
  • 95
  • 125
  • The committee is undecided about whether that's the correct behavior. I think we're free to innovate here. https://cplusplus.github.io/LWG/issue539 – Eric Niebler Sep 11 '17 at 02:40