Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class Template specialization for multiple types

I found a few questions that ask something similar but could not find a straight answer for my particular case. The whole syntax for Templates is very confusing to me so I may just misunderstood something.

I have a class template that is supposed to accept every type. Simple example:

template <class T>
class State {
  public:
    void set(T newState);
    T get();
  private:
    T state;
};

template <class T>
void State<T>::set(T newState){
  state = newState;
}

template <class T>
T State<T>::get(){
  return state;
}

Now I would like to have a specialised template for a group of types that adds an additional function for these types. From what I found out so far I can utilize so called type_traits but how exactly they are used to achieve this is still a mystery to me.

F.e. this specialization for the int type but instead of writing this just for the int type I would also like to allow all other int and float variants. I found std::is_arithmetic but have no Idea how to utilize it to achieve this.

template <>
class State <int> {
  public:
    void set(int newState);
    int get();
    int multiplyState(int n);
  private:
    int state;
};

void State<int>::set(int newState){
  state = newState;
}

int State<int>::get(){
  return state;
}

int State<int>::multiplyState(int n){
  return state*n;
}
like image 586
timonsku Avatar asked Mar 29 '19 15:03

timonsku


People also ask

Can there be more than one arguments to templates?

Can there be more than one argument to templates? Yes, like normal parameters, we can pass more than one data type as arguments to templates.

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.

Are template specializations inline?

An explicit specialization of a function template is inline only if it is declared with the inline specifier (or defined as deleted), it doesn't matter if the primary template is inline.

What is the difference between generic class template and specialization template?

Generics are generic until the types are substituted for them at runtime. Templates are specialized at compile time so they are not still parameterized types at runtime. The common language runtime specifically supports generics in MSIL.


1 Answers

You can use partial template specialization in combination with SFINAE to achieve this:

#include <type_traits>

template <class T, typename = void>
class State
{
    T state;

public:
    void set(T newState)
    {
        state = newState;
    }

    T get()
    {
      return state;
    }
};

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{
    T state;

public:
    void set(int newState)
    {
        state = newState;
    }

    int get()
    {
        return state;
    }

    int multiplyState(int n)
    {
        return state*n;
    }
};

live example here

The trick here lies in the use of the second template parameter (which can be unnamed and is given a default argument). When you use a specialization of your class template, e.g., State<some_type>, the compiler has to figure out which of the templates should be used. To do so, it has to somehow compare the given template arguments with each template and decide which one is the best match.

The way this matching is actually done is by trying to deduce the arguments of each partial specialization from the given template arguments. For example, in the case of State<int>, the template arguments are going to be int and void (the latter is there because of the default argument for the second parameter of the primary template). We then try to deduce the arguments for our sole partial specialization

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>;

from the template arguments int, void. Our partial specialization has a single parameter T, which can directly be deduced from the first template argument to be int. And with that, we're already done as we have deduced all parameters (there is only one here). Now we substitute the deduced parameters into the partial specialization: State<T, std::enable_if_t<std::is_arithmetic_v<T>>>. We end up with State<int, void>, which matches the list of initial arguments of int, void. Therefore, the partial template specialization applies.

Now, if, instead, we had written State<some_type>, where some_type is not an arithmetic type, then the process would be the same up to the point where we have successfully deduced the parameter for the partial specialization to be some_type. Again, we substitute the parameter back into the partial specialization State<T, std::enable_if_t<std::is_arithmetic_v<T>>>. However, std::is_arithmetic_v<some_type> will now be false, which will lead to std::enable_if_t<…> not being defined and substitution fails. Since substituion failure is not an error in this context, this simply means that the partial specialization is not an option here and the primary template will be used instead.

If there were multiple matching partial specializations, they then would have to be ranked to pick the best match. The actual process is quite complicated, but it generally boils down to picking the most concrete specialization.

like image 134
Michael Kenzel Avatar answered Oct 16 '22 14:10

Michael Kenzel