Нет ли стандартного хэша для `std :: filesystem :: path`? - PullRequest
0 голосов
/ 27 июня 2018

У меня есть простая программа, предназначенная для хранения набора объектов 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&>{},

Вот живая версия Coliru , если вы хотите играть.

Неужели не определено 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)") << ' ';
}

Ответы [ 2 ]

0 голосов
/ 27 июня 2018
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 ); }
  };      
};

so hashing::smart_hasher - это хеш-объект, который будет искать hash_value(T const&) в пространстве имен T, и в случае неудачи будет использовать std::hash<T>, если он доступен, и, если нет, будет генерировать ошибку компилятора.

Если вы хотите написать дополнительные хэши для типов std, создайте перегрузку функции hash_value в hashing::adl. Для других типов создайте его в связанном с ним пространстве имен. Например, если вы хотите поддержать хеширование tuple s:

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, автоматически выбирает хеш для всего, что обеспечивает эту настройку.

0 голосов
/ 27 июня 2018

Поскольку существует std::filesystem::hash_value, который является частью стандарта, почему этот код не компилируется, если я не предоставлю свой собственный std::hash?

Да, есть fs::hash_value(), но нет специализации std::hash<fs::path>, что вам и понадобится. Вот почему это не компилируется. Что касается того, почему библиотека предоставляет первую функцию, но не последнюю, я процитирую из Билли О'Нила (реализатор стандартной библиотеки MSVC):

Похоже на дефект.

Однако указывать пути в качестве ключей в хеш-таблице почти наверняка неправильно; Вы должны проверить эквивалентность пути в большинстве таких сценариев. То есть "/foo/bar/../baz" и "/foo/baz" являются одной и той же целью, но не одним и тем же путем. Точно так же, "./bar" и "./bar" могут быть разными путями, в зависимости от значения current_path в первом контексте и во втором.

Если вам нужны канонически уникальные пути, тогда просто std::unordered_set<fs::path> не будет делать то, что вы хотите в любом случае. Так что, может быть, неудачная компиляция - это не плохо? Я не знаю достаточно о файловой системе, чтобы сказать так или иначе.

<ч />

Обратите внимание, что вы сами, предоставляя специализацию std::hash для fs::path, не допускается - вы можете добавлять специализации к std только для типов, которыми вы управляете. Типы, которые будут называться «программно-определяемыми типами». fs::path - это не тот тип, которым вы управляете, поэтому вы не можете специализировать std::hash для него.

...