Learning Lin
Learning Lin

Reputation: 157

Why can't ranges be used if in a function?

I'm trying to get a range like python like below:

#include <iostream>
#include <ranges>

auto Range(double start, double end, double step)
{
    if (start <= end) {
        auto step_fun = [=](auto x) { return x * step + start; };
        auto end_pred = [=](auto x) { return x <= end; };
        auto range =
            std::views::iota(0)
            | std::views::transform(step_fun)
            | std::views::take_while(end_pred);
        return range;
    }
    else {
        auto step_fun = [=](auto x) { return -x * step + start; };
        auto end_pred = [=](auto x) { return x >= end; };
        auto range =
            std::views::iota(0)
            | std::views::transform(step_fun)
            | std::views::take_while(end_pred);
        return range;
    }
}
int main() {
    auto range = Range(108, 100, 1);

    for (auto i : range)
        std::cout << i << ' ';
    return 0;
}

But the Visual Studio told me:

Error   C3487   'std::ranges::take_while_view<std::ranges::transform_view<std::ranges::iota_view<_Ty,std::unreachable_sentinel_t>,Range::<lambda_3>>,Range::<lambda_4>>': all return expressions must deduce to the same type: previously it was 'std::ranges::take_while_view<std::ranges::transform_view<std::ranges::iota_view<_Ty,std::unreachable_sentinel_t>,Range::<lambda_1>>,Range::<lambda_2>>'   

and

Message     No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called    test    

But only start>endorstart<end,it can work,like:

auto Range(double start, double end, double step)
{
auto step_fun = [=](auto x) { return x * step + start; };
        auto end_pred = [=](auto x) { return x <= end; };
        auto range =
            std::views::iota(0)
            | std::views::transform(step_fun)
            | std::views::take_while(end_pred);
        return range;
}

How can I fix it?

Upvotes: 0

Views: 202

Answers (2)

Caleth
Caleth

Reputation: 62704

A function must have one return type, auto doesn't change that. Your two returns have different, incompatible types, because each lambda expression has a unique type.

The error message rather buried it: blah blah Range::<lambda_3>,Range::<lambda_4> vs blah blah Range::<lambda_1>,Range::<lambda_2>

You can do some arithmetic to reduce it to one return

auto Range(double start, double end, double step)
{
    if (start > end) {
        step *= -1;
    }
    int count = (end - start) / step;

    auto step_fun = [=](auto x) { return x * step + start; };
    return std::views::iota(0, count)
         | std::views::transform(step_fun);
}

Caveat: this function is still problematic if step is negative, you might instead want to throw if end is not reachable from start.

Upvotes: 4

apple apple
apple apple

Reputation: 10591

Because the result type of take_while and transform contains the function type as part of signature, but one function can only have single return type.

in addition to what already answered, you can also erase the type.

#include <iostream>
#include <ranges>
#include <functional>

auto Range(double start, double end, double step)
{
    std::function<double(double)> step_fun;
    std::function<double(double)> end_pred;

    if (start <= end) {
        step_fun = [=](auto x) { return x * step + start; };
        end_pred = [=](auto x) { return x <= end; };
    }
    else {
        step_fun = [=](auto x) { return -x * step + start; };
        end_pred = [=](auto x) { return x >= end; };    
    }
    auto range =
            std::views::iota(0)
            | std::views::transform(step_fun)
            | std::views::take_while(end_pred);
    return range;
}
int main() {
    auto range = Range(108, 100, 1);

    for (auto i : range)
        std::cout << i << ' ';
    return 0;
}

Upvotes: 1

Related Questions