Is there anyway to specialize a template like this, making the specialization apply only if T has a member function hash
? (Note this is only an example of what I am trying to do. I know that it would make more sense for each class that has a hash
function to check it on its own in the operator==
member function, but I just want to know if this kind of thing 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.
Member functions can be function templates in several contexts. All functions of class templates are generic but are not referred to as member templates or member function templates. If these member functions take their own template arguments, they are considered to be member function templates.
The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
Here's an example from my own code. As you might guess from one of the structure names this is based on the principle that Substitution Failure is Not an Error. The structure has_member_setOrigin
defines two versions of test
. The first one cannot be satisfied if U
does not have a member setOrigin
. Since that is not an error in a template substitution it just acts as if it does not exist. The resolution order for polymorphic functions thus finds test(...)
which would otherwise have a lower priority. The value
is then determined by the return type of test
.
This is followed by two definitions of callSetOrigin
(equivalent to your equals
) using the enable_if
template. If you examine enable_if
you'll see that if the first template argument is true then enable_if<...>::type
is defined, otherwise it is not. This again creates a substitution error in one of the definitions of callSetOrigin
such that 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
{
}
Forgot I provided a definition of enable_if
as well:
#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_ */
A C++11 solution. Just modify the return type such that it's only valid types that have .hash
. Also, we use the comma operator so that, while the compiler will check that it can compute declval<T>.hash()
, it will actually ignore that and use the type of true
, which is of course 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)
is the same as decltype(Y)
(thanks the comma operator). That means that my return type here is basically decltype(true)
, i.e. bool
, as desired. So why do I have declval<T>.hash()
? Isn't that just a waste of space?
The answer is that it is the test that T
has a hash
method. If this test fails, then the computation of the return type fails, and therefore the function is not seen as a valid overload and the compiler will look elsewhere.
Finally, if you haven't seen declval
before, it's a really useful way to create an object of type T
in an unevaluated context (such as decltype
). You might be tempted to write T()
to construct a T
, and therefore use T().hash()
to call hash
. But that won't work if T
doesn't have a default constructor. declval
solves this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With