给定c++ 20中的std::ranges::range
,我如何确定该范围内值的类型?
我想写一个函数,使std::vector
的任意范围。我希望这个函数有一个漂亮的显式声明。比如:
template<std::ranges::range Range>
std::vector<std::value_type_t<Range>> make_vector(Range const&);
下面的代码似乎可以工作,但是声明不显式,实现也很难看(甚至忽略了它没有在可能的情况下预先分配正确的大小)。
template<std::ranges::range Range>
auto make_vector(Range const& range)
{
using IteratorType = decltype(std::ranges::begin(std::declval<Range&>()));
using DerefType = decltype(*std::declval<IteratorType>());
using T = std::remove_cvref_t<DerefType>;
std::vector<T> retval;
for (auto const& x: range) {
retval.push_back(x);
}
return retval;
}
是否有规范/更好/更短/更好的方法来做这件事?
您正在寻找的类型特征拼写为std::ranges::range_value_t
,而不是std::value_type_t
。
同样,你在这里试图写的整个函数只是c++ 23中std::ranges::to
的一个更有限的版本。
让我们按顺序看一遍:
template<std::ranges::range Range>
auto make_vector(Range const& range)
这是检查Range
是否是一个范围,但range
不是Range
,而是const Range
。有可能R
是range
,但R const
不是,所以你实际上并没有正确地约束这个函数。
正确的约束应该是:
template<typename Range>
requires std::ranges::range<Range const>
auto make_vector(Range const& range)
但是这会限制您只能使用const-iterable范围(这是一个不必要的限制),然后要求您在整个主体中非常小心地使用Range const
(这很容易忘记)。
这两个都是为什么,对于范围,你会想要使用转发引用:
template<std::ranges::range Range>
auto make_vector(Range&& range)
这将适当地约束你的功能。
下:using IteratorType = decltype(std::ranges::begin(std::declval<Range&>()));
using DerefType = decltype(*std::declval<IteratorType>());
using T = std::remove_cvref_t<DerefType>;
这些东西有直接的类型特征:
using IteratorType = std::ranges::iterator_t<Range>;
using DerefType = std::iter_reference_t<IteratorType>;
或:
using DerefType = std::ranges::range_reference_t<Range>;
但也因为你想要的值类型,这是(已经指出):
using T = std::ranges::range_value_t<Range>;
注意,值类型不一定只是去掉限定符的引用类型。
最后:
std::vector<T> retval;
for (auto const& x: range) {
retval.push_back(x);
}
return retval;
将元素转发到push_back
(即auto&& x
然后FWD(x)
,而不是auto const& x
和x
)将更有效甚至更正确。
另外,您至少需要:
if constexpr (std::ranges::sized_range<Range>) {
retval.reserve(std::ranges::size(range));
}
因为如果我们有可用的大小,那么最好将分配减少到一个。
最后,在c++ 23中,make_vector(r)
可以直接拼写为std::ranges::to<std::vector>(r)
。这做了我提到的分配优化,但可以额外提高性能,因为vector
的内部构造可以避免不断检查是否需要额外的分配(这是push_back
必须做的)。