{std :: filesystem :: path`没有标准哈希吗?

时间:2018-06-27 14:21:15

标签: c++ hash c++17

我有一个简单的程序,旨在存储一组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

hashtest.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)") << ' ';
}

3 个答案:

答案 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"