I need to specialize template member function for some type (let's say double). It works fine while class X
itself is not a template class, but when I make it template GCC starts giving compile-time errors.
#include <iostream>
#include <cmath>
template <class C> class X
{
public:
template <class T> void get_as();
};
template <class C>
void X<C>::get_as<double>()
{
}
int main()
{
X<int> x;
x.get_as();
}
here is the error message
source.cpp:11:27: error: template-id
'get_as<double>' in declaration of primary template
source.cpp:11:6: error: prototype for
'void X<C>::get_as()' does not match any in class 'X<C>'
source.cpp:7:35: error: candidate is:
template<class C> template<class T> void X::get_as()
How can I fix that and what is the problem here?
Thanks in advance.
It doesn't work that way. You would need to say the following, but it is not correct
template <class C> template<>
void X<C>::get_as<double>()
{
}
Explicitly specialized members need their surrounding class templates to be explicitly specialized as well. So you need to say the following, which would only specialize the member for X<int>
.
template <> template<>
void X<int>::get_as<double>()
{
}
If you want to keep the surrounding template unspecialized, you have several choices. I prefer overloads
template <class C> class X
{
template<typename T> struct type { };
public:
template <class T> void get_as() {
get_as(type<T>());
}
private:
template<typename T> void get_as(type<T>) {
}
void get_as(type<double>) {
}
};
If one is able to used std::enable_if
we could rely on SFINAE (substitution failure is not an error)
that would work like so (see LIVE):
#include <iostream>
#include <type_traits>
template <typename C> class X
{
public:
template <typename T,
std::enable_if_t<!std::is_same_v<double,T>, int> = 0>
void get_as() { std::cout << "get as T" << std::endl; }
template <typename T,
std::enable_if_t<std::is_same_v<double,T>, int> = 0>
void get_as() { std::cout << "get as double" << std::endl; }
};
int main() {
X<int> d;
d.get_as<double>();
return 0;
}
The ugly thing is that, with all these enable_if's only one specialization needs to be available for the compiler otherwise disambiguation error will arise. Thats why the default behaviour "get as T" needs also an enable if.
Probably the cleanest way to do this in C++17 and on-wards is to use a if constexpr
in combination with the std::is_same_v
type trait without explicitly specialisation at all:
#include <iostream>
#include <type_traits>
template <typename C>
class X {
public:
template <typename T>
void get_as() {
// Implementation part for all types
std::cout << "get as ";
// Implementation part for each type separately
if constexpr (std::is_same_v<double, T>) {
std::cout << "'double'";
} else if constexpr (std::is_same_v<int, T>) {
std::cout << "'int'";
} else {
std::cout << "(default)";
}
// Implementation part for all types
std::cout << std::endl;
return;
}
};
int main() {
X<int> d {};
d.get_as<double>(); // 'double'
d.get_as<int>(); // 'int'
d.get_as<float>(); // (default)
return EXIT_SUCCESS;
}
If you need to have a return type as well you could declare the return type as auto
:
template <typename T>
auto get_as() {
if constexpr (std::is_same_v<double, T>) {
return 0.5;
} else {
return 0;
}
}
Success story sharing
type<>
wrapper? couldn't a cast of a 0 to a pointer of typeT
do the trick? I guess it's not as elegant...template<typename T> void get_as(T*); void get_as(double*);
and pass a(T*)0
.