Use of 'auto […] 'before deduction of 'auto' with recursive, concept-based function template

后端 未结 1 1969
谎友^
谎友^ 2021-01-05 04:23

I wanted to create a deep_flatten function template that would produce a range of elements that are deeply joined. For example, if we

相关标签:
1条回答
  • There are two problems here.

    The first problem is yours:

    namespace rng {
        template <std::ranges::range Rng>
        auto deep_flatten(Rng&& rng) {
            using namespace std::ranges;
    
            if constexpr (range<Rng>) { // <==
                return deep_flatten(rng | views::join);
            } else {
                return rng | views::join;
            }
        }
    }
    

    This function is infinitely recursive. deep_flatten is constrained range<Rng>, so the if constexpr check there is always going to be true, so we're never going to enter the base case. This is just a bug - we're checking the wrong thing, it's not if we're a range, it's if our underlying value is a range. That's:

    namespace rng {
        template <typename Rng>
        auto deep_flatten(Rng&& rng) {
            using namespace std::ranges;
    
            auto joined = rng | views::join;    
            if constexpr (range<range_value_t<decltype(joined)>>) {
                return deep_flatten(joined);
            } else {
                return joined;
            }
        }
    }
    

    And here we get into the second problem, which is the standard library's problem. What rng | views::join means is:

    The name views​::​join denotes a range adaptor object ([range.adaptor.object]). Given a subexpression E, the expression views​::​join(E) is expression-equivalent to join_­view{E}.

    But join_view{E} for an E that's already a specialization of join_view... is a no-op right now because of class template argument deduction (CTAD) - the copy deduction candidate is the best candidate, so our nested join operation actually becomes a single join. Your original implementation gets around this problem because it's not join-ing a join_view, it's always join-ing vectors.

    I've submitted LWG 3474.

    In the meantime, we can work around the views::join problem by just directly using join_view and specifying the template argument explicitly:

    namespace rng {
        template <typename Rng>
        auto deep_flatten(Rng&& rng) {
            using namespace std::ranges;
    
    
            auto joined = join_view<views::all_t<Rng>>(rng);
    
            if constexpr (range<range_value_t<decltype(joined)>>) {
                return deep_flatten(joined);
            } else {
                return joined;
            }
        }
    }
    

    This works.

    0 讨论(0)
提交回复
热议问题