Is it legal to specialize std library functions for shared_ptr of a user-defined type?

The standard says the following about specializing templates from the standard library (through What can and cannot I specialize in the std namespace? )

The program can add a specialization template for any standard library template for the namespace only std if the declaration depends on the user type and the specialization meets the requirements of the standard library for the original template and is not explicitly prohibited.

Is it legal to specialize standard library templates with a standard library class specialized with a user-defined class?

For example, specializing in std::hash for std::shared_ptr<MyType> ?

From the paragraph read above and the related question, this sounds as it should be, since the declaration of specialization depends on MyType , however, "if it is not explicitly forbidden," bothers me a little.

The example below compiles and works as expected (AppleClang 7.3), but is it legal?

 #include <unordered_set> #include <memory> #include <cassert> #include <string> struct MyType { MyType(std::string id) : id(id) {} std::string id; }; namespace std { template<> struct hash<shared_ptr<MyType>> { size_t operator()(shared_ptr<MyType> const& mine) const { return hash<string>()(mine->id); } }; template<> struct equal_to<shared_ptr<MyType>> { bool operator()(shared_ptr<MyType> const& lhs, shared_ptr<MyType> const& rhs ) const { return lhs->id == rhs->id; } }; } int main() { std::unordered_set<std::shared_ptr<MyType>> mySet; auto resultA = mySet.emplace(std::make_shared<MyType>("A")); auto resultB = mySet.emplace(std::make_shared<MyType>("B")); auto resultA2 = mySet.emplace(std::make_shared<MyType>("A")); assert(resultA.second); assert(resultB.second); assert(!resultA2.second); } 
+5
source share
1 answer

Yes, it is legal.

At some point, it is even dubious to specialize in std::shared_ptr<int> ; I do not know if they corrected this ambiguity in the standard as a defect or not.

Note that this is a poor hash implementation for global use. Firstly, because it does not support null common pointers. Secondly, since hashing a common pointer, as always, the value of int is doubtful. This is even dangerous, because if a generic pointer to an int in the container has this int change, you just broke the program.

Think about how to create your own hash for such cases.

 namespace notstd { template<class T, class=void> struct hasher_impl:std::hash<T>{}; namespace adl_helper { template<class T> std::size_t hash( T const& t, ... ) { return ::notstd::hasher_impl<T>{}(t); } }; namespace adl_helper2 { template<class T> std::size_t hash_helper(T const& t) { using ::notstd::adl_helper::hash; return hash(t); } } template<class T> std::size_t hash(T const& t) { return ::notstd::adl_helper2::hash_helper(t); } struct hasher { template<class T> std::size_t operator()(T const& t)const { return hash(t); } }; } 

Now it allows 3 settings.

First, if you override std::size_t hash(T const&) in the namespace containing T , it selects it.

Otherwise, if you specialize in notstd::hasher_impl<T, void> for your type T , it selects it.

Thirdly, if both of them do not work, it calls std::hash<T> , picking up any specializations.

Then you can do:

 std::unordered_set<std::shared_ptr<MyType>, ::notstd::hasher> mySet; 

and add:

 struct MyType { MyType(std::string id) : id(id) {} std::string id; friend std::size_t hash( MyType const& self) { return ::notstd::hash(self.id); } friend std::size_t hash( std::shared_ptr<MyType> const& self) { if (!self) return 0; return ::notstd::hash(*self); } }; 

which should give you a smart hash on shared_ptr<MyType> .

This prevents the danger of someone changing the id to shared_ptr<MyType> , which splits each container containing shared_ptr<MyType> non-local way.

General condition is the devil; consider writing a copy on the record pointer if you are really worried that it is expensive.

+3
source

Source: https://habr.com/ru/post/1265100/


All Articles