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.