[英]Is std::views::keys guaranteed to work correctly with any pair/tuple type?
代码粘贴在这里 & https://www.godbolt.org/z/qszqYsT4o
我正在尝试提供与 c++20 视图兼容的boost::adaptors::indexed
功能。 我的主要用例是std::vector
,但我肯定更喜欢通用解决方案。 我震惊地发现std::views::keys
没有按预期正确提取第一个元素。
对于 GCC 10.3,rng1 的rng1
是垃圾,而rng2
符合预期。 GCC 10.4+ 工作正常。 最新版clang无法编译代码。
问题:
std::views::keys
是否保证支持任何对/元组类型,还是我的代码是 UB?std::span
,但似乎无法让它自然地工作。 注意:如果 std::views::iota 可用,我会很乐意将std::views::zip
与std::views::iota
一起使用。#include <vector>
#include <ranges>
#include <iostream>
template <typename T>
using IndexValuePair = std::pair<std::size_t, std::reference_wrapper<T>>;
template <typename T, typename Allocator>
auto make_indexed_view(std::vector<T, Allocator>& vec)
{
auto fn = [&vec](T& val) {
return IndexValuePair<T>{static_cast<std::size_t>(&val - vec.data()), val};
};
return std::views::all(vec) | std::views::transform(fn);
}
template <typename T, typename Allocator>
auto make_indexed_view(const std::vector<T, Allocator>& vec)
{
auto fn = [&vec](const T& val) {
return IndexValuePair<const T>{static_cast<std::size_t>(&val - vec.data()), val};
};
return std::views::all(vec) | std::views::transform(fn);
}
struct GetFirstSafely {
template <typename T1, typename T2>
const T1& operator()(const std::pair<T1, T2>& p) const { return std::get<0>(p); }
template <typename T1, typename T2>
T1 operator()(std::pair<T1, T2>&& p) const { return std::get<0>(std::move(p)); }
};
auto get_first = [](auto&& p) -> decltype(auto) { return GetFirstSafely{}(std::forward<decltype(p)>(p)); };
int main()
{
const std::vector<int> v{10, 20, 30};
auto fn = [](const auto& val) { return val.second >= 20; };
auto rng1 = make_indexed_view(v) | std::views::filter(fn) | std::views::keys;
auto rng2 = make_indexed_view(v) | std::views::filter(fn) | std::views::transform(get_first);
for (auto&& elem : rng1)
std::cout << elem << '\n';
for (auto&& elem : rng2)
std::cout << elem << '\n';
return 0;
}
std::views::keys
是否保证支持任何对/元组类型,还是我的代码是 UB?
views::keys
保证可以在 C++20 中使用pair
/ tuple
,并且由于P2165保证可以在 C++23 中使用tuple-like
对象。
鉴于 clang 14.0.0 无法编译,我的代码是否合法 C++?
您的代码格式正确。
但是,没有必要使用reference_wrapper
,一个简单的pair<size_t, T&>
就足够了,也没有必要使用std::views::all(vec)
, vec | views::transform(fn)
vec | views::transform(fn)
将自动将vec
转换为view
。
有没有更好的方法在 c++20 中实现这个功能?
在 C++20 中,恐怕不是。 在 C++23 中,您可以编写views::iota
和views::zip
来执行此操作,例如
const std::vector<int> v{10, 20, 30};
for (const auto& [index, value] : views::zip(views::iota(0uz, v.size()), v))
std::cout << index << " " << value << "\n";
在 C++26 中,您可以使用views::enumerate
(如果采用)
const std::vector<int> v{10, 20, 30};
for (const auto & e : views::enumerate(v))
std::cout << e.index << " " << e.value << "\n";
这是LWG 3502 。 由于该特定问题,您的代码在 gcc 10.3 上失败,但它已得到解决,您的代码在 gcc 10.4(或 11.1 等)上运行良好。
问题中的示例应该看起来很熟悉:
std::vector<int> vec = {42};
auto r = vec | std::views::transform([](auto c) { return std::make_tuple(c, c); })
| std::views::keys;
它失败了,因为elements_view
的operator*
被指定为:
constexpr decltype(auto) operator*() const { return get<N>(*current_); }
当第N
个元素是纯右值时(就像在那个例子和你的例子中一样),这是一个立即悬空的右值引用。 该问题的解决方案确保operator*
在这些情况下返回一个纯右值,因此一切正常。
请注意,没有理由写:
return std::views::all(vec) | std::views::transform(fn);
范围适配器已经为您做到了。 你可以写:
return vec | std::views::transform(fn);
可以将std::span
用于make_indexed_view
以使其更通用。 当然,修改底层序列的结构(例如调用std::vector::push_back
)是未定义的行为。
template <typename T, std::size_t Extent>
auto make_indexed_view(const std::span<T, Extent>& span)
{
auto fn = [span](T& val) {
return IndexValuePair<T>{static_cast<std::size_t>(&val - span.data()), val};
};
return span | std::views::transform(fn);
}
int main()
{
constexpr int data[] = {10, 20, 30};
auto fn = [](const auto& elem) { return elem.second >= 20; };
auto rng = make_indexed_view(std::span{data}) | std::views::filter(fn) | std::views::transform(get_first);
for (auto&& elem : rng) {
std::cout << elem << '\n';
}
return 0;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.