How to use another class as a specialization of a class template

I have a hybrid lock class that rotates a lock attempt for a fixed-amount amount of time (compile time) before returning to lock on std::mutex until the lock is available.

Simplified:

 #include <mutex> template<unsigned SPIN_LIMIT> class hybrid_lock { public: void lock(){ for(unsigned i(0);i<SPIN_LIMIT;++i){ if(this->mMutex.try_lock()){ return; } } this->mMutex.lock(); } void unlock(){ this->mMutex.unlock(); } private: std::mutex mMutex; }; 

In the particular case of SPIN_LIMIT==0 this returns to the "simple" std::mutex (i.e. there are no visible spins).

So, I specialized in:

 template<> class hybrid_lock<0> : public std::mutex {}; 

It works fine, but is there an approved way to specialize class templates to be another (pre-existing) template?

+6
source share
3 answers

Note. I answered the actual question, not the headline.

Well, now hybird_lock<0> and hybird_lock<1> are completely different, one comes from std::mutex , and the other contains / wraps it. This changes the overall value of hybird_lock and the value behind it. That is, they are not semantically the same. This can lead to some rather unexpected consequences - hybird_lock<0> inherits a lot of things, which would not be in other cases.

If this were the only difference, I would not have started specializing at all. Remember that a null register will be known at compile time, and however that may be, the whole loop will be fully optimized.

If there were other important (or actual) optimizations, I would go for something like:

 template<> class hybrid_lock<0> { public: void lock(){ this->mMutex.lock(); } void unlock(){ this->mMutex.unlock(); } private: std::mutex mMutex; }; 

This implementation makes 0 special case, and not something completely different.

+2
source

There is no “official” way to do this, but here is a good way - with templates it is often better to break the main class of the template into smaller classes of “action” or “function”. Thus, you get more control and detail on specialization, which means that you need to maintain the main logic in one place:

 #include <iostream> #include <mutex> // general form of the spin_locker template<unsigned SPIN_LIMIT, class Mutex> struct spinner { static void lock(Mutex& m) { for (unsigned i = 0 ; i < SPIN_LIMIT ; ++i) if (m.try_lock()) return; m.lock(); } }; // optmised partial specialisation for zero spins template<class Mutex> struct spinner<0, Mutex> { static void lock(Mutex& m) { m.lock(); } }; template<unsigned SPIN_LIMIT, class Mutex = std::mutex> class hybrid_lock { using spinner_type = spinner<SPIN_LIMIT, Mutex>; public: void lock(){ spinner_type::lock(mMutex); } void unlock(){ mMutex.unlock(); } std::unique_lock<Mutex> make_lock() { return std::unique_lock<Mutex>(mMutex); } private: Mutex mMutex; }; // since only the 'spinner' functor object needs specialising there is now no need to specialise the main logic using namespace std; auto main() -> int { hybrid_lock<100> m1; hybrid_lock<0> m2; hybrid_lock<100, std::recursive_mutex> m3; hybrid_lock<0, std::recursive_mutex> m4; auto l1 = m1.make_lock(); auto l2 = m2.make_lock(); auto l3 = m3.make_lock(); auto l4 = m4.make_lock(); return 0; } 
+1
source

Richard Hodges' answer is great, but you can just overload the lock method:

 #include <iostream> #include <type_traits> #include <mutex> template<class Mutex = std::mutex> struct hybrid_lock { template<int N> void lock(std::integral_constant<int, N> val){ for (unsigned i = 0 ; i < val() ; ++i) if (mMutex.try_lock()) return; mMutex.lock(); } void lock(std::integral_constant<int, 0>){ mMutex.lock(); } void unlock(){ mMutex.unlock(); } std::unique_lock<Mutex> make_lock() { return std::unique_lock<Mutex>(mMutex); } private: Mutex mMutex; }; template <int N> constexpr std::integral_constant<int, N> IC; int main() { hybrid_lock<> m1; hybrid_lock<> m2; m1.lock(IC<0>); m2.lock(IC<100>); return 0; } 

IC variable template , from C ++ 14, if your compiler does not support it, you can use an alias of type enter an alias :

 template<int N> using IC = std::integral_constant<int, N>; 

and use it like this:

 m.lock(IC<0>{}); 
+1
source

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


All Articles