Is there a way to specialize a template based on parameter members in C ++?

In any case, in order to specialize such a template so that specialization is applied only if T has a hash member function? (Note that this is just an example of what I'm trying to do. I know that for every class that hash has a function, to check it in the operator== function, it makes sense, but I just want to know if this is possible .)

 template <class T> bool equals(const T &x, const T &y) { return x == y; } template <class T> // somehow check if T has a member function 'hash' bool equals<T>(const T &x, const T &y) { return x.hash() == y.hash() && x == y; } 

I would prefer a pre-C ++ 11 solution if possible.

+6
source share
2 answers

Here is an example from my own code. As you can guess from one of the names of the structures, this is based on the principle that the Replacement Error is not an error . The has_member_setOrigin structure defines two versions of test . The first cannot be executed if U does not have a member of setOrigin . Since this is not a mistake in pattern substitution, it simply acts as if it does not exist. Thus, the resolution order for polymorphic functions finds test(...) , which otherwise would have a lower priority. Then value determined by the return type test .

The following are two callSetOrigin definitions (equivalent to your equals ) using the enable_if pattern. If you examine enable_if , you will see that if the first argument of the template is true, then enable_if<...>::type defined, otherwise it is not. This again creates a replacement error in one of the callSetOrigin definitions, so only one survives.

 template <typename V> struct has_member_setOrigin { template <typename U, void (U::*)(const Location &)> struct SFINAE {}; template <typename U> static char test(SFINAE<U, &U::setOrigin>*); template <typename U> static int test(...); static const bool value = sizeof(test<V>(0)) == sizeof(char); }; template<typename V> void callSetOrigin(typename enable_if <has_member_setOrigin<V>::value, V>::type &p, const Location &loc) const { p.setOrigin(loc); } template<typename V> void callSetOrigin(typename enable_if <!has_member_setOrigin<V>::value, V>::type &p, const Location &loc) const { } 

I forgot I also gave the definition of enable_if :

 #ifndef __ENABLE_IF_ #define __ENABLE_IF_ template<bool _Cond, typename _Tp> struct enable_if { }; template<typename _Tp> struct enable_if<true, _Tp> { typedef _Tp type; }; #endif /* __ENABLE_IF_ */ 
+8
source

C ++ Solution 11. Just change the return type so that it is only a valid type that has .hash . In addition, we use the comma operator, so although the compiler checks that it can evaluate declval<T>.hash() , it will actually ignore it and use the type true , which of course is bool , the type you want.

 template <class T> auto equals(const T &x, const T &y) -> decltype(declval<T>.hash(), true) { return x.hash() == y.hash() && x == y; } 

I believe this is called Expression SFINAE.

More details:

decltype(X,Y) same as decltype(Y) (thanks to the comma operator). This means that my return type here is basically decltype(true) , i.e. bool , optional. Why do I have declval<T>.hash() ? Isn't that a waste of space?

The answer is that this is a test that T has a hash method. If this test fails, then the calculation of the return type fails, and therefore the function is not considered as a valid overload, and the compiler will look elsewhere.

Finally, if you have not seen declval , this is a really useful way to create an object of type T in an invaluable context (e.g. decltype ). You might be tempted to write T() to build T and therefore use T().hash() to call hash . But this will not work if T does not have a default constructor. declval solves this.

+2
source

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


All Articles