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;
}
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.
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.
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.
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.
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.
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