Home > Net >  std::unordered_set<std::filesystem::path>: compile error on clang and g below v.12. Bug or u
std::unordered_set<std::filesystem::path>: compile error on clang and g below v.12. Bug or u

Time:09-01

I added the following function template to my project, and a user complained that it wouldn't compile on their system anymore:

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();
}

The error message with g 9.4 was approximately

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;

So the error arose when instantiating the above function template with T = std::filesystem::path. I investigated a little and found that there was no issue when instantiating it with other types, e.g. fundamental types or std::string, but only with std::filesystem::path.

Using Compiler Explorer, I looked at how different compiler versions treat the code and found that only g v.12 can compile the instantiation with std::filesystem::path. Any g version below 12 fails with the above error. clang produces a similar error (call to implicitly deleted default constructor), even on the newest version (14). I didn't test other compilers.

The workaround I used was substituting std::unordered_set with std::set. Then it works on g v.8 and clang v.7 and above.

So I guess the error is a missing hashing function for std::filesystem::path? Or is it an error on my part?

CodePudding user response:

The std::hash specialization for std::filesystem::path has only recently been added as resolution of LWG issue 3657 into the standard draft. It hasn't been there in the published C 17 and C 20 standards.

There has always been however a function std::filesystem::hash_value from which you can easily create a function object to pass as hash function to 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;

If you are providing that template without any guarantees that it works on types other than those that have a std::hash specialization defined, then I think there is no problem on your part.

However, if you require the type to be hashable, it would be a good idea to let the user override the hash function used the same way std::unordered_set does. The same also applies to the equality functor used.

  • Related