CPP Reference:
A common mistake is to declare two function templates that differ only
in their default template arguments. This does not work because the
declarations are treated as redeclarations of the same function
template (default template arguments are not accounted for in function
template equivalence).
/*** WRONG ***/
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
typename = std::enable_if_t<std::is_integral<Integer>::value>
>
T(Integer) : m_type(int_t) {}
template <typename Floating,
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
>
T(Floating) : m_type(float_t) {} // error: treated as redefinition
};
/* RIGHT */
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
std::enable_if_t<std::is_integral<Integer>::value, int> = 0
>
T(Integer) : m_type(int_t) {}
template <typename Floating,
std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
>
T(Floating) : m_type(float_t) {} // OK
};
Care should be taken when using enable_if in the type of a template
non-type parameter of a namespace-scope function template. Some ABI
specifications like the Itanium ABI do not include the
instantiation-dependent portions of non-type template parameters in
the mangling, meaning that specializations of two distinct function
templates might end up with the same mangled name and be erroneously
linked together. For example:
// first translation unit
struct X {
enum { value1 = true, value2 = true };
};
template<class T, std::enable_if_t<T::value1, int> = 0>
void func() {} // #1
template void func<X>(); // #2
// second translation unit
struct X {
enum { value1 = true, value2 = true };
};
template<class T, std::enable_if_t<T::value2, int> = 0>
void func() {} // #3
template void func<X>(); //#4
The function templates #1 and #3 have different signatures and are
distinct templates. Nonetheless, #2 and #4, despite being
instantiations of different function templates, have the same mangled
name in the Itanium C++ ABI (_Z4funcI1XLi0EEvv), meaning that the
linker will erroneously consider them to be the same entity.
Fix:
#include <iostream>
#include <type_traits>
// Enable if "T" is integral
template <typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0
>
void print(T value)
{
std::cout << "Integral: " << value << std::endl;
}
// Enable if "T" is not integral
template <typename T,
std::enable_if_t<!std::is_integral_v<T>, int> = 0
>
void print(T value)
{
std::cout << "Not Integral: " << value << std::endl;
}
int main()
{
int i = 42;
print(i);
double d = 42.0;
print(d);
}
Better yet, use concepts:
#include <iostream>
#include <concepts>
// Enable if "T" is integral
template <std::integral T>
void print(T value)
{
std::cout << "Integral: " << value << std::endl;
}
// Enable if "T" is not integral
template <typename T>
void print(T value)
{
std::cout << "Not Integral: " << value << std::endl;
}
int main()
{
int i = 42;
print(i);
double d = 42.0;
print(d);
}
LIVE