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]
要在将迭代器取消引用到给定范围时查找类型,您可以
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!
在下面的代码中,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]
要在将迭代器取消引用到给定范围时查找类型,您可以
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!