迭代容器或范围 - constness 问题
Iterating over container or range - problem with constness
我正在尝试编写一个模板函数,它将汇总某个集合的所有元素 - 指定为普通 stl 容器,或指定为 ranges-v3 的范围。 (实际的功能,如下所示更通用一点)我认为这会起作用:
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
using It = decltype(range.begin());
Ret sum = zero;
int numElements = 0;
for (It it = range.begin(); it != range.end(); ++it) {
sum += extract(*it);
++numElements;
}
return { sum, numElements };
}
这确实适用于 STL 元素,但不适用于范围。这给了我一个很长的错误:
<this file, at line 'using It'> error C2662: 'ranges::v3::basic_iterator<ranges::v3::adaptor_cursor<ranges::v3::basic_iterator<ranges::v3::adaptor_cursor<std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<_Ty>>>,ranges::v3::iter_transform_view<Rng,ranges::v3::indirected<Fun>>::adaptor<false>>>,ranges::v3::remove_if_view<ranges::v3::transform_view<Rng,Fun>,ranges::v3::logical_negate_<EnemyGroup::stepUpdate::<lambda_c582fb1297dce111c4572cef649d86b9>>>::adaptor>> ranges::v3::view_facade<Derived,ranges::v3::finite>::begin<Derived,false,0x0>(void)': cannot convert 'this' pointer from 'const Range' to 'ranges::v3::view_facade<Derived,ranges::v3::finite> &'
note: Conversion loses qualifiers
本来以为是ranges-v3的vs2015分支有些不足。没有多想,我只是简单地解决了一个问题:
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
using It = decltype(const_cast<Range*>(&range)->begin());
Ret sum = zero;
int numElements = 0;
for (It it = const_cast<Range*>(&range)->begin(); it != const_cast<Range*>(&range)->end(); ++it) {
//sum += extract(std::as_const(*it)); (does not work either, converts to void)
sum += extract(*it);
++numElements;
}
return { sum, numElements };
}
但随着最新的 MSVC 预览版发布,ranges 的 master 分支现在得到了正式支持。然而,上述错误仍然存在。
- 将 range 的对象用作
const&
是错误的做法吗?我知道这些对象是轻量级的并且很容易复制,但是使用 const 引用应该不会有什么坏处,或者?另一方面,如果传递了具体的 STL 容器,我需要将其作为 const&
传递
- 如果使用
const&
不正确,是否有一些简单的方法可以让函数同时使用容器和范围,而无需在调用站点编写任何内容(例如调用 view::all
)
我正在使用 Visual Studio Community 2017,版本 15.9.3。请注意,在 15.9 之前,不支持其 master 分支中的 range-v3
。
既然你问我到底怎么称呼它。我的实际代码很复杂,但我将其简化为这个小例子:
#include <set>
#include <range/v3/view/filter.hpp>
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
using It = decltype(range.begin());
Ret sum = zero;
int numElements = 0;
for (It it = range.begin(); it != range.end(); ++it) {
sum += extract(*it);
++numElements;
}
return { sum, numElements };
}
int main() {
std::set<int*> units;
auto [vsum, num] = sum(
units | ranges::v3::view::filter([](const int* eu) { return *eu>0; }),
0,
[](const int* eu) { return *eu/2; }
);
}
这给了我与上面相同的转换错误。
并非所有范围都是 const
可迭代的。也就是说,存在范围类型 T
,其中 const T
不是范围。 filter
是一个经典的例子:它需要缓存从 begin
返回的迭代器的值,以便将来的调用是 O(1)(参见 http://eel.is/c++draft/range.filter.view#6)。因此,begin
不能成为 const
成员函数而不违反标准库策略,即 const
成员可从多个线程调用而不会引入数据竞争。
因此,const Range&
不适合接受一般 Range
参数,因为它适合接受 "container that I don't intend to modify." 我们建议采用 Range
参数的函数接受他们通过转发参考。如果您将程序更改为:
#include <set>
#include <range/v3/view/filter.hpp>
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(Range&& range, Ret zero, Func extract) { // Note "Range&&"
Ret sum = zero;
int numElements = 0;
for (auto&& e : range) {
sum += extract(e);
++numElements;
}
return { sum, numElements };
}
int main() {
std::set<int*> units;
auto [vsum, num] = sum(
units | ranges::v3::view::filter([](const int* eu) { return *eu>0; }),
0,
[](const int* eu) { return *eu/2; }
);
}
它将编译并 运行 正确。
我正在尝试编写一个模板函数,它将汇总某个集合的所有元素 - 指定为普通 stl 容器,或指定为 ranges-v3 的范围。 (实际的功能,如下所示更通用一点)我认为这会起作用:
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
using It = decltype(range.begin());
Ret sum = zero;
int numElements = 0;
for (It it = range.begin(); it != range.end(); ++it) {
sum += extract(*it);
++numElements;
}
return { sum, numElements };
}
这确实适用于 STL 元素,但不适用于范围。这给了我一个很长的错误:
<this file, at line 'using It'> error C2662: 'ranges::v3::basic_iterator<ranges::v3::adaptor_cursor<ranges::v3::basic_iterator<ranges::v3::adaptor_cursor<std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<_Ty>>>,ranges::v3::iter_transform_view<Rng,ranges::v3::indirected<Fun>>::adaptor<false>>>,ranges::v3::remove_if_view<ranges::v3::transform_view<Rng,Fun>,ranges::v3::logical_negate_<EnemyGroup::stepUpdate::<lambda_c582fb1297dce111c4572cef649d86b9>>>::adaptor>> ranges::v3::view_facade<Derived,ranges::v3::finite>::begin<Derived,false,0x0>(void)': cannot convert 'this' pointer from 'const Range' to 'ranges::v3::view_facade<Derived,ranges::v3::finite> &'
note: Conversion loses qualifiers
本来以为是ranges-v3的vs2015分支有些不足。没有多想,我只是简单地解决了一个问题:
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
using It = decltype(const_cast<Range*>(&range)->begin());
Ret sum = zero;
int numElements = 0;
for (It it = const_cast<Range*>(&range)->begin(); it != const_cast<Range*>(&range)->end(); ++it) {
//sum += extract(std::as_const(*it)); (does not work either, converts to void)
sum += extract(*it);
++numElements;
}
return { sum, numElements };
}
但随着最新的 MSVC 预览版发布,ranges 的 master 分支现在得到了正式支持。然而,上述错误仍然存在。
- 将 range 的对象用作
const&
是错误的做法吗?我知道这些对象是轻量级的并且很容易复制,但是使用 const 引用应该不会有什么坏处,或者?另一方面,如果传递了具体的 STL 容器,我需要将其作为const&
传递
- 如果使用
const&
不正确,是否有一些简单的方法可以让函数同时使用容器和范围,而无需在调用站点编写任何内容(例如调用view::all
)
我正在使用 Visual Studio Community 2017,版本 15.9.3。请注意,在 15.9 之前,不支持其 master 分支中的 range-v3
。
既然你问我到底怎么称呼它。我的实际代码很复杂,但我将其简化为这个小例子:
#include <set>
#include <range/v3/view/filter.hpp>
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
using It = decltype(range.begin());
Ret sum = zero;
int numElements = 0;
for (It it = range.begin(); it != range.end(); ++it) {
sum += extract(*it);
++numElements;
}
return { sum, numElements };
}
int main() {
std::set<int*> units;
auto [vsum, num] = sum(
units | ranges::v3::view::filter([](const int* eu) { return *eu>0; }),
0,
[](const int* eu) { return *eu/2; }
);
}
这给了我与上面相同的转换错误。
并非所有范围都是 const
可迭代的。也就是说,存在范围类型 T
,其中 const T
不是范围。 filter
是一个经典的例子:它需要缓存从 begin
返回的迭代器的值,以便将来的调用是 O(1)(参见 http://eel.is/c++draft/range.filter.view#6)。因此,begin
不能成为 const
成员函数而不违反标准库策略,即 const
成员可从多个线程调用而不会引入数据竞争。
因此,const Range&
不适合接受一般 Range
参数,因为它适合接受 "container that I don't intend to modify." 我们建议采用 Range
参数的函数接受他们通过转发参考。如果您将程序更改为:
#include <set>
#include <range/v3/view/filter.hpp>
template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(Range&& range, Ret zero, Func extract) { // Note "Range&&"
Ret sum = zero;
int numElements = 0;
for (auto&& e : range) {
sum += extract(e);
++numElements;
}
return { sum, numElements };
}
int main() {
std::set<int*> units;
auto [vsum, num] = sum(
units | ranges::v3::view::filter([](const int* eu) { return *eu>0; }),
0,
[](const int* eu) { return *eu/2; }
);
}
它将编译并 运行 正确。