【问题标题】:Use of 'auto [...] 'before deduction of 'auto' with recursive, concept-based function template使用基于概念的递归函数模板在扣除“auto”之前使用“auto [...]”
【发布时间】:2020-11-24 16:38:34
【问题描述】:

我想创建一个deep_flatten 函数模板,该模板将生成一个range 的元素,这些元素是joined。例如,如果我们只考虑嵌套的std::vectors,我可以有:

template <typename T>
struct is_vector : public std::false_type { };

template <typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type { };

template <typename T>
auto deepFlatten(const std::vector<std::vector<T>>& vec) {
    using namespace std::ranges;
    if constexpr (is_vector<T>::value) {
        auto range = vec | views::join;
        return deepFlatten(std::vector(range.begin(), range.end()));
    } else {
        auto range = vec | views::join;
        return std::vector(range.begin(), range.end());
    }
}

这使我能够做到:

std::vector<std::vector<std::vector<int>>> nested_vectors = {
        {{1, 2, 3}, {4, 5}, {6}},
        {{7},       {8, 9}, {10, 11, 12}},
        {{13}}
};

std::ranges::copy(
        deep_flatten(nested_vectors),
        std::ostream_iterator<int>(std::cout, " ")
);

按预期将以下文本打印到控制台中:

1 2 3 4 5 6 7 8 9 10 11 12 13

但是,我不太喜欢这个解决方案。它不仅效率低下(创建许多临时向量),而且它也仅适用于std::vectors。我想我可以使用更多的 魔法并使用std::ranges::range 概念:

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

在我看来这很简单——我们有一个std::ranges::range,我们检查它的值类型。根据它是否是嵌套范围,我们递归或简单地返回 joined 元素。

很遗憾,它不起作用。尝试运行后:

int main() {
    std::set<std::vector<std::list<int>>> nested_ranges = {
            {{1, 2, 3}, {4, 5}, {6}},
            {{7},       {8, 9}, {10, 11, 12}},
            {{13}}
    };

    std::ranges::copy(
            rng::deep_flatten(nested_ranges),
            std::ostream_iterator<int>(std::cout, " ")
    );
}

我收到一条错误消息:

In instantiation of 'auto rng::deep_flatten(Rng&&) [with Rng = std::ranges::join_view<std::ranges::ref_view<std::set<std::vector<std::__cxx11::list<int> > > > >]':
     required from 'auto rng::deep_flatten(Rng&&) [with Rng = std::set<std::vector<std::__cxx11::list<int> > >&]'
     required from here
     error: use of 'auto rng::deep_flatten(Rng&&) [with Rng = std::ranges::join_view<std::ranges::ref_view<std::set<std::vector<std::__cxx11::list<int> > > > >]' before deduction of 'auto'
     39 |             return deep_flatten(rng | views::join);
        |                    ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~

研究了类似的问题,我无法真正理解为什么会出现此错误。

我正在使用gcc version 10.1.0 (Rev3, Built by MSYS2 project)

【问题讨论】:

    标签: c++20 c++ c++20 c++-concepts std-ranges


    【解决方案1】:

    这里有两个问题。

    第一个问题是你的:

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

    这个函数是无限递归的。 deep_flatten 受到 range&lt;Rng&gt; 的约束,所以 if constexpr 的检查总是正确的,所以我们永远不会进入基本情况。这只是一个错误——我们正在检查错误的东西,这不是我们是否是一个范围,而是我们的基础值是否是一个范围。那是:

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

    这里我们进入第二个问题,即标准库的问题。 rng | views::join 的意思是:

    名称views​::​join 表示范围适配器对象([range.adaptor.object])。给定子表达式E,表达式views​::​join(E)join_­view{E} 等价。

    但是 join_view{E} 对于已经是 join_view... 的特化的 E... 由于类模板参数推导 (CTAD),现在是无操作的 - 复制推导候选者是最佳候选者,所以我们嵌套的join 操作实际上变成了单个join。您最初的实现解决了这个问题,因为它不是join-ing a join_view,而是始终join-ing vectors。

    我已提交LWG 3474

    与此同时,我们可以通过直接使用join_view 并明确指定模板参数来解决views::join 问题:

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

    这行得通。

    【讨论】:

    • 提交时能否分享 LWG 问题的链接?那就太好了,谢谢。
    • 谢谢你,巴里。第一个错误是我在重构代码时的疏忽——尽管如此,还是感谢您指出这一点,因为它肯定会帮助我在将来注意到这些事情。关于第二个,我想第二个 cigien 的评论 - 请您在提交问题时分享链接吗?
    • @cigien 添加了链接。
    • @Fureeish 已添加链接
    • @NoSenseEtAl 这不可能是故意行为。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-06
    • 2011-06-08
    相关资源
    最近更新 更多