我将以下函数模板添加到我的项目中,用户抱怨它无法再在他们的系统上编译:
template<typename T>
std::size_t removeDuplicates(std::vector<T>& vec)
{
std::unordered_set<T> seen;
auto newEnd = std::remove_if(
vec.begin(), vec.end(), [&seen](const T& value)
{
if (seen.find(value) != std::end(seen))
return true;
seen.insert(value);
return false;
}
);
vec.erase(newEnd, vec.end());
return vec.size();
}
带有
g++ 9.4
的错误消息大约为
error: use of deleted function
'std::unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set()
[with
_Value = std::filesystem::__cxx11::path;
_Hash = std::hash<std::filesystem::__cxx11::path>;
_Pred = std::equal_to<std::filesystem::__cxx11::path>;
_Alloc = std::allocator<std::filesystem::__cxx11::path>]'
12 | std::unordered_set<T> seen;
所以用
T = std::filesystem::path
实例化上面的函数模板时就出现了错误。
我调查了一下,发现用其他类型实例化它时没有问题,例如基本类型或 std::string
,但仅限于 std::filesystem::path
。
使用 Compiler Explorer,我查看了不同编译器版本如何处理代码,发现只有
g++ v.12
可以用 std::filesystem::path
编译实例化。任何低于 12 的 g++
版本都会失败并出现上述错误。即使在最新版本 (14) 上,clang
也会产生类似的错误 (call to implicitly deleted default constructor
)。我没有测试其他编译器。
我使用的解决方法是将
std::unordered_set
替换为 std::set
。然后它适用于 g++ v.8
和 clang v.7
及以上。
所以我猜错误是缺少
std::filesystem::path
的哈希函数?还是我的失误?
std::hash
std::filesystem::path
专业化最近才作为 LWG 问题 3657 的决议添加到标准草案中。它在已发布的 C++17 和 C++20 标准中尚未出现。
std::filesystem::hash_value
,您可以轻松地创建一个函数对象以作为哈希函数传递给 std::unordered_set
:
struct PathHash {
auto operator()(const std::filesystem::path& p) const noexcept {
return std::filesystem::hash_value(p);
}
};
//...
std::unordered_set<std::filesystem::path, PathHash> seen;
如果您提供的模板没有任何保证它适用于定义了
std::hash
专业化的类型以外的类型,那么我认为您没有问题。
但是,如果您要求类型可散列,那么让用户以与
std::unordered_set
相同的方式覆盖散列函数将是一个好主意。这同样适用于所使用的等式函子。
补充一下std::hashstd::filesystem::path是在C++17中添加的答案,它实际上是在以下版本的标准库中实现的:
C++ 标准库 | 版本 |
---|---|
GCC libstdc++ | v11.4 |
Clang libc++ | 17 |
MSVC STL | 19.32 |