Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there anyway to specialize a template based on the members of a parameter in C++?

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.

like image 760
Matt Avatar asked Jun 19 '12 19:06

Matt


People also ask

Can a member function be a template?

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.

What is a specialized template?

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.

Can a template be a template parameter?

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.)

How will you restrict the template for a specific datatype?

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.


2 Answers

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_ */
like image 132
Ben Jackson Avatar answered Nov 03 '22 13:11

Ben Jackson


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.

like image 20
Aaron McDaid Avatar answered Nov 03 '22 13:11

Aaron McDaid