'std::filesystem::p ath' 没有标准哈希吗?

Is there no standard hash for `std::filesystem::path`?

本文关键字:标准 哈希吗 std filesystem ath      更新时间:2023-10-16

我有一个简单的程序,旨在存储一组C++17std::filesystem::path对象。 既然有一个std::filesystem::hash_value是标准的一部分,为什么在我不必提供自己的std::hash的情况下编译此代码?

当我使用 gcc 8.1.1 作为g++ -std=c++17 -NO_HASH=1 hashtest.cpp -o hashtest -lstdc++fs进行编译和链接时,我的哈希函数包含在内,一切都运行良好。 但是,如果我将其更改为-NO_HASH=0,我会收到一长串错误消息,其中关键之一是:

usr/include/c++/8/bits/hashtable.h:195:21: error: static assertion failed: hash function must be invocable with an argument of key type
static_assert(__is_invocable<const _H1&, const _Key&>{},

如果你想玩,这里有一个现场的Coliru版本。

真的没有定义std::hash<std::filesystem::path>吗?我错过了什么?

对于那些对我为什么想要这样的东西感兴趣的人,它是这样的:https://codereview.stackexchange.com/questions/124307/from-new-q-to-compiler-in-30-seconds

哈希测试.cpp

#include <optional>
#include <unordered_set>
#include <filesystem>
#include <string>
#include <iostream>
namespace fs = std::filesystem;
#if NO_HASH
namespace std {
template <>
struct hash<fs::path> {
std::size_t operator()(const fs::path &path) const {
return hash_value(path);            }
};
}
#endif
int main()
{
using namespace std::literals;
std::unordered_set< std::optional<fs::path> >  paths = {
"/usr/bin"s, std::nullopt, "/usr//bin"s, "/var/log"s
};
for(const auto& p : paths)
std::cout << p.value_or("(no path)") << ' ';
}

既然有一个std::filesystem::hash_value是标准的一部分,为什么在我不必提供自己的std::hash的情况下编译此代码?

是的,有一个fs::hash_value()但没有std::hash<fs::path>专业化,这是你需要的。这就是它不编译的原因。至于为什么库提供前一个函数而不是后者,我将引用 Billy O'Neal(MSVC 标准库的实现者(:

看起来像一个缺陷。

但是,将路径作为键放在哈希表中几乎肯定是不正确的;在大多数此类情况下,您需要测试路径等效性。也就是说,"/foo/bar/../baz""/foo/baz"是相同的目标,但路径不同。同样,"./bar""./bar"可能是不同的路径,具体取决于第一个上下文中current_path的值与第二个上下文中的值。

如果你想要的是规范上唯一的路径,那么std::unordered_set<fs::path>无论如何都不会做你想做的事。所以也许编译失败不是一件坏事?我对文件系统的了解还不够多,无法以一种或另一种方式说出来。


请注意,您自己不允许为fs::path提供std::hash专用化 - 您只能为您控制的类型向std添加专用化。将称为"程序定义类型"的类型。fs::path不是您控制的类型,因此您无法专门针对它进行std::hash

解决方案是显式编写和使用我自己的哈希。

#include <optional>
#include <unordered_set>
#include <filesystem>
#include <string>
#include <iostream>
namespace fs = std::filesystem;
struct opt_path_hash {
std::size_t operator()(const std::optional<fs::path>& path) const {
return path ? hash_value(path.value()) : 0;
}
};
int main()
{
using namespace std::literals;
std::unordered_set< std::optional<fs::path>, opt_path_hash >  paths = {
"/usr/bin"s, std::nullopt, "/usr//bin"s, "/var/log"s
};
for(const auto& p : paths)
std::cout << p.value_or("(no path)") << 'n';
}

这将生成以下输出,正确折叠两个版本的"/usr/bin"

"/var/log"
"(no path)"
"/usr/bin"
namespace hashing {
namespace adl {
template<class T, class...Ts>
auto hash_value( T const& t, Ts&&... )
-> std::result_of_t< std::hash<T>&&(T const&) >
{
return std::hash<T>{}(t);
}
template<class T>
auto hasher_private( T const& t )
-> decltype( hash_value( t ) )
{ return hash_value(t); }
}
struct smart_hasher {
template<class T>
auto operator()( T const& t ) const
->decltype( adl::hasher_private( t ) )
{    return adl::hasher_private( t ); }
};      
};

所以hashing::smart_hasher是一个哈希对象,它将在T的命名空间中查找hash_value(T const&),如果失败,将使用std::hash<T>如果可用,否则将生成编译器错误。

如果要为std类型编写其他哈希器,请在hashing::adl中创建hash_value函数重载。 对于其他类型,请在其关联的命名空间中创建它。 例如,如果要支持tuples的哈希处理:

namespace hashing::adl {
template<class...Ts>
std::size_t hash_value( std::tuple<Ts...> const& tup ) {
// get hash values and combine them here
// use `smart_hasher{}( elem ) to hash each element for
// recursive smart hashing
}
}

现在,任何使用smart_hasher的人都会自动为提供该自定义的任何内容选择哈希器。