Pat*_*ryk 10 c++ c++20 std-ranges
我想通过转换另一个向量来初始化一个向量。我用两种内联初始化和转换的方式进行了测试std::vector。
一种使用 lambda 内联初始化(使用std::transform):
std::vector<int> foo(100,42);
const auto fooTimesTwo = [&]{
std::vector<int> tmp(foo.size());
std::transform(foo.begin(), foo.end(), tmp.begin(), convert);
return tmp;
}();
Run Code Online (Sandbox Code Playgroud)
而另一个 - 使用std::ranges::views::transform:
std::vector<int> foo(100,42);
auto transform_range = (foo | std::ranges::views::transform(convert));
std::vector<int> fooTimesTwo {
transform_range.begin(),
transform_range.end()
};
Run Code Online (Sandbox Code Playgroud)
我希望两种向量初始化方式都应该具有相似的性能,但由于某种原因,传统解决方案的基准测试std::transform比第二个方法快得多(快 9.7 倍 -> https://quick-bench.com/q/3PSDRO9UbMNunUpdWGNShF49WlQ) .
我的问题是:
std::ranges::views::transform不当吗?旁注 - 可以使用 来完成boost::make_transform_iterator,但我无法在 quick-bench 上检查它,因为它们不支持 boost。所以我不确定这种解决方案的效率。
Bar*_*rry 18
为什么它的工作速度那么慢?
您遇到的问题是 C++98/C++17 迭代器模型和 C++20 迭代器模型之间的差异之一。X成为前向迭代器的旧要求之一是:
if
X是一个可变迭代器,reference是对T; ifX是一个常量迭代器,reference是对 的引用const T,
也就是说,迭代器的reference类型必须是真正的引用。它不能是代理引用或纯右值。任何重复值reference是prvalue自动只是一个输入迭代器。
C++20 中没有这样的要求。
所以如果你看foo | std::ranges::views::transform(convert),这是一个纯右值的范围int。在 C++20 迭代器模型中,这是一个随机访问范围。但是在 C++17 中,因为我们处理的是纯右值,所以这只是一个输入范围。
vector的迭代器对构造函数不是基于 C++20 迭代器模型,而是基于 C++98/C++17 迭代器模型。它使用迭代器类别的旧理解,而不是新理解。并且 C++20 范围适配器非常努力地确保它们对旧的迭代器模型做“正确的事情”。我们改编的范围在检查为 C++20 时正确地将自己宣传为随机访问,并在检查为 C++17 时输入:
void f(std::vector<int> v) {
auto r = v | std::views::transform(convert);
using R = decltype(r);
static_assert(std::ranges::random_access_range<R>);
static_assert(std::same_as<std::input_iterator_tag,
std::iterator_traits<std::ranges::iterator_t<R>>::iterator_category>);
}
Run Code Online (Sandbox Code Playgroud)
那么当你将两个输入迭代器传递给vector的迭代器对构造函数时会发生什么呢?好吧,它不能预先分配一个巨大的块(我们不能在last - first这里做,因为它是一个输入迭代器,具有“大小哨兵”可能独立于遍历类别的概念也是 C+ 中的一个新事物+20 迭代器模型)...相反,它基本上是这样做的:
for (; first != last; ++first) {
push_back(*first);
}
Run Code Online (Sandbox Code Playgroud)
对于输入迭代器,没有比这更好的了。但这非常低效,因为我们最终会进行八次分配而不是一次分配。
在 range-v3 中,您可以这样做:
auto result = foo | ranges::views::transform(convert)
| ranges::to<std::vector>();
Run Code Online (Sandbox Code Playgroud)
该to算法理解C ++ 20迭代模型,并通过这里提前预约做正确的事。然而,to它是一个外部库,我们不能只是修改标准库类型来选择它,这一点非常有限。我们希望std::ranges::to在 C++23 中有一个对标准库容器进行改进的版本,以便更好地做到这一点。在这一点上,这个解决方案将比你原来的解决方案更好,因为std::vector<int> foo(tmp.size())它本身是浪费的,因为必须对一块内存进行零初始化,然后立即覆盖它。
与此同时,我确实想知道保留这个reference必须引用的要求的一般价值(很少有人知道,可能更少依赖:最大的价值可能只是知道operator->()可以返回&operator*()?)。
std::vector<bool> 例如,已经在这方面撒谎,并将自己宣传为随机访问 C++17 范围。
标准库实现虽然应该能够更好地处理这种情况。他们应该能够安全地检查 C++20 迭代器概念并因此做一些智能的事情。在这种情况下,我们有 C++20 随机访问迭代器,因此vector 应该能够在这种情况下有效地构造自己。提交100070。
| 归档时间: |
|
| 查看次数: |
359 次 |
| 最近记录: |