std::erase_if 封装为何报 Conversion loses qualifiers?
结论
vec 的类型是 const std::vector<T>&,而 std::erase_if 需要修改容器,要求传入非 const 的 vector 引用,因此编译报错。object 的 const 属性与这次参数不匹配无关。
如果必须保留当前签名,并允许调用者传入真正的 const vector,就无法安全地从原容器中删除元素。只有能保证底层 vector 本身不是 const 时,才可以考虑用 const_cast 恢复可修改的引用。
为什么三种写法都会报错
std::erase_if 的 vector 重载要求第一个参数的类型为:
std::vector<T, Alloc>&
封装函数传入的却是:
const std::vector<T>&
将后者绑定到前者需要去掉 const。编译器不允许隐式进行这种转换,因此报告 Conversion loses qualifiers。
删除元素会改变 vector 的大小,还可能移动后续元素、销毁被删除的元素,都需要修改容器。
三种 lambda 写法调整了比较值的捕获方式、元素参数的传递方式,或者创建了 object 的副本,但都没有改变 vec 是 const 引用这一点,所以会遇到相同的问题。给 lambda 添加 mutable 也无法改变 vec 的类型。
签名必须保持时如何处理
如果可能传入真正的 const vector
现有签名允许这样的调用:
const std::vector<int> values{1, 2, 2, 3};
vector_removeIfEquals(values, 2);
这里的 values 本身就是 const 对象。即使通过强制转换取得可修改的引用,实际修改它仍会导致未定义行为。
在这种约束下,只能复制容器,再从副本中删除元素,原容器不会改变。但当前函数返回 void,也没有其他输出参数,无法把过滤后的副本交给调用者。如果需要返回过滤结果,就必须调整接口,例如改为返回新的 vector。接受真正的 const vector、保留当前签名和原地删除元素这三个要求无法同时满足。
如果能保证底层 vector 实际上不是 const
如果接口无法修改,但调用约定能保证传入的始终是非 const vector,可以显式去掉引用上的 const:
#include <vector>
template <typename T>
inline void vector_removeIfEquals(
const std::vector<T>& vec,
const T& object)
{
auto& mutableVec = const_cast<std::vector<T>&>(vec);
std::erase_if(mutableVec, [value = T(object)](const T& t) {
return t == value;
});
}
例如,这里的 values 是非 const 对象:
std::vector<int> values{1, 2, 2, 3};
vector_removeIfEquals(values, 2);
// values 为 {1, 3}
这里的 const_cast 恢复了访问原本非 const 对象的可修改引用,不能把真正的 const 对象变成可修改对象。函数签名仍然允许调用者传入真正的 const vector,因此必须由调用方保证这个前提。
能修改接口时的常规写法
原地删除函数应将 vec 声明为非 const 引用,用参数类型表达修改意图。object 保留 const 引用即可:
#include <vector>
template <typename T>
inline void vector_removeIfEquals(
std::vector<T>& vec,
const T& object)
{
std::erase_if(vec, [value = T(object)](const T& t) {
return t == value;
});
}
如果签名由外部框架或既有协议规定,应先确认该接口是否允许修改传入的容器。
比较值和使用条件
示例在删除前复制了 object,使整个删除过程使用固定的比较值。这要求 T 可复制,并支持相应的 const 相等比较。
如果能保证 object 不引用 vec 内部的元素,也可以省去这次复制:
std::erase_if(vec, [&object](const T& t) {
return t == object;
});
这里的 vec 必须已经是非 const 引用。如果 object 引用容器内部的元素,删除时的元素移动可能改变该位置的值,或使引用失效。复制比较值能稳定匹配条件,但被引用的容器元素本身仍可能受到删除操作的影响。
std::erase_if 从 C++20 起提供,需要启用 C++20 或更高标准,并使用支持该接口的标准库。vector 对应的重载由 <vector> 提供。
备注:内容仅供参考。
