ranges::views::enumerate 按引用还是按值捕获?我们怎么知道?

ranges::views::enumerate capturing by reference or by value? How can we tell?

在下面的代码中,auto [i, e]auto&& [i, ee] 都绑定 std::pair<int, T&> 而不是 std::pair<int, T>。有人可以解释一下如果不进行经验测试怎么会知道这一点?我假设它是 range-v3 实现。是否存在您想要使用 auto&& 而不是 auto 的情况?

auto container = std::array<int,3>{ 1,2,3 };

for (std::pair<int, int> p : ranges::views::enumerate(container))
    p.second = 0; //Bad, of course 
print(container);

for (auto [i, e] : ranges::views::enumerate(container))
    e = 0; //Okay???
print(container);

for (auto&& [i, ee] : ranges::views::enumerate(container))
    ee = 42; //Okay???
print(container);

> [1,2,3]
> [0,0,0]
> [42,42,42]

https://godbolt.org/z/b7vrsxqK4

要在将迭代器取消引用到给定范围时查找类型,您可以

template<typename... Args> void whatis();
int main() 
{
    auto container = std::array<int, 3>{ 1, 2, 3 };
    auto it = ranges::views::enumerate(container).begin();
    using traits = std::iterator_traits<decltype(it)>;
    whatis<traits::reference>();
}

这将告诉您 for(auto p : enumerate(container)) 中的推断类型将是 ranges::common_pair<unsigned int,int &>

同样可以发现ranges::views::enumerate(container)的类型是ranges::detail::index_view<unsigned int,int>,struct ranges::ref_view<class std::array<int,3>>>.

您可以从那里开始查看源代码。 enumerate 传递给 zip,后者传递给 all,后者使用模板规范化解析

/// If it's a view already, pass it though.
template<typename T>
static constexpr auto from_range_(T&& t, std::true_type, detail::ignore_t,
    detail::ignore_t)
{
    return static_cast<T&&>(t);
}

/// If it is container-like, turn it into a view, being careful
/// to preserve the Sized-ness of the range.
template<typename T>
static constexpr auto from_range_(T&& t, std::false_type, std::true_type,
    detail::ignore_t)
{
    return ranges::views::ref(t);
}

/// Not a view and not an lvalue? If it's a borrowed_range, then
/// return a subrange holding the range's begin/end.
template<typename T>
static constexpr auto from_range_(T&& t, std::false_type, std::false_type,
    std::true_type)
{
    return make_subrange(static_cast<T&&>(t));
}

public:
template(typename T)(
    /// \pre
    requires range<T&> AND viewable_range<T>)
    constexpr auto operator()(T&& t) const
{
    return all_fn::from_range_(static_cast<T&&>(t),
        meta::bool_<view_<uncvref_t<T>>>{},
        std::is_lvalue_reference<T>{},
        meta::bool_<borrowed_range<T>>{});
}

然后您可以查看 ref_view,看看它确实按照名称所暗示的那样工作(指的是底层容器)。

我很困惑,因为通常在范围 for 循环中,当在迭代器上调用 * 运算符时,它 returns 一个 T&,所以你必须添加一个 ref-qualifier 以获得参考而不是副本。

for(auto& el : container)
--------------- 
auto& el = *it; // reference
for(auto el : container)
--------------- 
auto el = *it; // copy

然而,当 * returns a std::pair<int, T&> 时你不能再 auto&,但现在 auto 将正常工作,因为它是参考在本地“复制”的那对内部不是它所指的东西。

将此与结构化绑定结合起来,实际发生的事情非常令人困惑,因为下面的 el 实际上是一个参考:

for (auto [i,el] : views::enumerate(container))
        el = 3; // OKAY!