有一个类似的问题:check if elements of a range can be moved?
我认为其中的答案并不是一个好的解决方案。实际上,它需要对所有容器进行部分特化。
我尝试过,但我不确定仅检查operator*()
是否足够。
// RangeType
using IteratorType = std::iterator_t<RangeType>;
using Type = decltype(*(std::declval<IteratorType>()));
constexpr bool canMove = std::is_rvalue_reference_v<Type>;
更新
这个问题可以分成两部分:
- STL 中的算法,比如
std::copy
/std::uninitialized_copy
,在接收右值元素时,是否可以避免不必要的深拷贝? - 当接收到一个 右值范围 时,如何检查它是像
std::ranges::subrange
这样的 范围适配器,还是像std::vector
这样持有其元素所有权的容器?
template <typename InRange, typename OutRange>
void func(InRange&& inRange, OutRange&& outRange) {
using std::begin;
using std::end;
std::copy(begin(inRange), end(inRange), begin(outRange));
// Q1: if `*begin(inRange)` returns a r-value,
// would move-assignment of element be called instead of a deep copy?
}
std::vector<int> vi;
std::list<int> li;
/* ... */
func(std::move(vi), li2);
// Q2: Would elements be shallow copy from vi?
// And if not, how could I implement just limited count of overloads, without overload for every containers?
// (define a concept (C++20) to describe those who take ownership of its elements)
正如@Nicol Bolas、@eerorika和@Davis Herring所指出的那样,Q1并不是问题,这也不是我困惑的原因。(但我确实认为API很混乱,std::assign
/std::uninitialized_construct
可能是更理想的名称)
@alfC对我的问题(Q2)做了很好的回答,并提供了一个清晰的观点。(范围移动语义)
总之,对于大多数当前容器(特别是那些来自STL的容器),(以及每个范围适配器...),部分特化/重载函数是唯一的解决方案,例如:
template <typename Range>
void func(Range&& range) { /*...*/ }
template <typename T>
void func(std::vector<T>&& movableRange) {
auto movedRange = std::ranges::subrange{
std::make_move_iterator(movableRange.begin()),
std::make_move_iterator(movableRange.end())
};
func(movedRange);
}
// and also for `std::list`, `std::array`, etc...
*it
将是一个xvalue。这就是该类型的全部意义:如果it
是移动迭代器,则T t = *it;
将执行移动。 - Nicol Bolasstd::forward
吗? - eerorika