我有一个简单的程序,旨在存储一组C ++ 17 std::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&>{},
如果您想玩,这里是live Coliru version。
真的没有定义的std::hash<std::filesystem::path>
吗?我缺少什么?
对于那些对为什么我想要这样的东西感兴趣的人来说,就是这样:https://codereview.stackexchange.com/questions/124307/from-new-q-to-compiler-in-30-seconds
#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)") << ' ';
}
答案 0 :(得分:7)
由于有一个
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>
就不会做您想要的任何事情。所以也许它不能编译不是一件坏事吗?我对文件系统的了解还不够多,无法说一种或另一种方式。
请注意,您自己不能为std::hash
提供fs::path
的特殊化-您只能为自己控制的类型向std
添加特殊化。称为“程序定义类型”的类型。 fs::path
不是您控制的类型,因此您无法对其专门化std::hash
。
答案 1 :(得分:1)
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
是一个哈希对象,它将在hash_value(T const&)
的命名空间中寻找T
,如果失败,将使用std::hash<T>
(如果可用),否则将使用std
生成编译器错误。
如果要为hash_value
类型编写其他散列,请在hashing::adl
中创建一个tuple
函数重载。对于其他类型,请在其关联的名称空间中创建它。例如,如果您想支持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
现在,使用{{1}}的任何人都会自动从哈希器中获取任何提供该自定义功能的东西。
答案 2 :(得分:0)
解决方案是显式编写并使用我自己的哈希。
#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"