consider this code:
#include <iostream>
#include <type_traits>
#include <string>
struct A{
int a;
};
struct B{
std::string b;
};
template<typename T>
auto has_member_a(...) -> std::false_type;
template<typename T>
auto has_member_a(int v) -> decltype(std::declval<T>().a, std::true_type{});
template<typename T>
auto has_member_b(...) -> std::false_type;
template<typename T>
auto has_member_b(int v) -> decltype(std::declval<T>().b, std::true_type{});
template <typename T, std::enable_if_t<decltype(has_member_a<T>(3))::value>>
void customPrint()
{
std::cout<<"Has member a\n";
}
template <typename T, std::enable_if_t<decltype(has_member_b<T>(3))::value>>
void customPrint()
{
std::cout<<"Has member b\n";
}
int main()
{
A a{5};
B b{"Ciao"};
customPrint<A>();
customPrint<B>();
return 0;
}
Now although the syntax is correct, the compiler gives a redefinition error because it's not able to distinguish between the two (no overloading can happen) since the input parameters are the same.
However, changing the template clauses to:
template <typename T, std::enable_if_t<decltype(has_member_a<T>(3))::value>* = nullptr>
makes everything works fine. Why though? What makes adding * = nullptr>
everything fine?
You didn't include your exact error message, but I guess it's something around
<source>:35:6: note: template argument deduction/substitution failed:
<source>:74:17: note: couldn't deduce template parameter '<anonymous>'
74 | customPrint<A>();
so it is not exactly because of disambiguating, but about compiler's inability to deduce the value of your second template parameter, which is of type
typename std::enable_if<decltype (has_member_b<T>(3))::value, void>::type
which is void
. Now there are a couple of different ways to fix it, you can e.g. use some integral type instead of void:
template <typename T, std::enable_if_t<decltype(has_member_b<T>(3))::value, int> >
and instantiate your templates like this (because compiler still cannot deduce the value of your int
template parameter, but at least int
type can have a value)
customPrint<A, 0>();
customPrint<B, 0>();
The easy way to avoid writing 0
explicitly, is to just give the second template parameter a default value:
template <typename T, std::enable_if_t<decltype(has_member_b<T>(3))::value, int> = 0>
And now even more popular way (imho) to do it is to use pointer type (in this example it's void*
), which can be assigned nullprt
value:
template <typename T, std::enable_if_t<decltype(has_member_a<T>(3))::value>* = nullptr>
The last syntax is probably the shortest version you could get.