Let\'s say, I have six types, and they each belong in a conceptual category.
Here is a diagram that shows this:
As an alternative to category selection via "traits", you can also consider CRTP (where the type carries the category as a base):
template<class Derived> class category1 {};
template<class Derived> class category2 {};
class A1: public category1<A1> { ..... };
class A2: public category2<A2> { ..... };
class B1: public category1<B1> { ..... };
class B2: public category2<B2> { ..... };
template<class T>void funcion_on1(category1<T>& st)
{
T& t = static_cast<T&>(st);
.....
}
template<class T>void funcion_on1(category2<T>& st)
{
T& t = static_cast<T&>(st);
.....
}
The advantage is to have a less polluted namespace.
Two functions signatures are not allowed to differ only by the default value of a template parameter. What would happen if you explicitly called function< int, void >
?
Usual usage of enable_if
is as the function return type.
//Handle all types from Category 1
template<class T >
typename std::enable_if<Is_Type_From_Category_1<T>::value>::type
function(T t){
//do category 1 stuff to the type
return;
}
//Handle all types from Category 2
template<class T >
typename std::enable_if<Is_Type_From_Category_2<T>::value>::type
function(T t){
//do category 2 stuff to the type
return;
}
I learned the following technique from R. Martinho Fernandes. The code shown below is written to illustrate the bare bones of the problem but you should refer to this blog post to get the full range of tricks to make it pretty.
You've already mentioned that you're running into problems because of signatures being identical. The trick is to make the types to be different.
Your second approach is close, but we can't use void
as the resulting type of the std::enable_if<>
.
Note that the following code does not compile, and specifying void
for the std::enable_if<>
does not change anything since the default is void
anyway.
#include <iostream>
class A {};
class B {};
template <
typename T,
typename = typename std::enable_if<std::is_same<T, A>::value>::type>
void F(T) {
std::cout << "A" << std::endl;
}
template <
typename T,
typename = typename std::enable_if<std::is_same<T, B>::value>::type>
void F(T) {
std::cout << "B" << std::endl;
}
int main() {
F(A{});
F(B{});
}
The reason, as you already described is because the signatures are identical. Let's differentiate them.
#include <iostream>
class A {};
class B {};
template <
typename T,
typename std::enable_if<std::is_same<T, A>::value, int>::type = 0>
void F(T) {
std::cout << "A" << std::endl;
}
template <
typename T,
typename std::enable_if<std::is_same<T, B>::value, int>::type = 0>
void F(T) {
std::cout << "B" << std::endl;
}
int main() {
F(A{});
F(B{});
}
Prints:
A
B
We have now differentiated the types between the 2 functions because rather than the second template parameter being a type, it is now an int
.
This approach is preferable to using std::enable_if<>
in the return type for example since constructors don't have return types, the pattern wouldn't be applicable for those.
Notes: std::is_same<>
is used with a single class to simplify the condition.
I think using tag despatch would be easier than SFINAE.
template<class T>
struct Category;
template<>
struct Category<Type_A> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_B> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_C> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_D> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_E> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_F> : std::integral_constant<int, 2> {};
template<class T>
void foo(std::integral_constant<int, 1>, T x)
{
// Category 1 types.
}
template<class T>
void foo(std::integral_constant<int, 2>, T x)
{
// Category 2 types.
}
template<class T>
void foo(T x)
{
foo(Category<T>(), x);
}