This is a question about how template deduction works when template parameter used as template parameter vs as default template parameter vs as return type.
As tested using GCC and VS compiliation of below snippet fails to deduce template parameter defined by std::enable_if_t
#include <iostream>
#include <type_traits>
template< class T, std::enable_if_t< std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )
{
left = left ^ right;
right = left ^ right;
left = left ^ right;
}
template< class T, std::enable_if_t< std::is_floating_point_v< T > > >
void SwapInPlace( T& left, T& right )
{
left = left - right;
right = left + right;
left = right - left;
}
int main()
{
int i1 = 10;
int i2 = -120;
std::cout << i1 << " " << i2 << std::endl;
SwapInPlace( i1, i2 );
std::cout << i1 << " " << i2 << std::endl;
double d1 = 1.1234;
double d2 = 2.5678;
std::cout << d1 << " " << d2 << std::endl;
SwapInPlace( d1 , d2 );
std::cout << d1 << " " << d2 << std::endl;
return 0;
}
VS: error C2783: 'void SwapInPlace(T &,T &)': could not deduce template argument for '__formal'
GCC: couldn't deduce template parameter -anonymous-
Declaring second template parameter as default template paramater makes deduction to work ok:
template< class T, class = std::enable_if_t< std::is_integral_v< T > > >
void SwapInPlace( T& left, T& right )
{...}
template< class T, class = std::enable_if_t< std::is_floating_point_v< T > >, bool = true >
void SwapInPlace( T& left, T& right )
{...}
Added bool = true
to second overload just to avoid compilation error because it's not possible to overload function template based on default template parameter. Want to focus only on the fact that deduction works ok here. If there was only one template using default parameter, e.g. for std::is_integral
, it would compile and work fine upon condition we pass correct parameters to it.
In case of return type everything compiles and works well:
template< class T >
std::enable_if_t < std::is_integral_v< T > >
SwapInPlace( T& left, T& right )
{...}
template< class T >
std::enable_if_t < std::is_floating_point_v< T > >
SwapInPlace( T& left, T& right )
{...}
Another way to make this code compile is by adding default value for template parameter defined by std::enable_if
. Added * = nullptr
before last closing angle bracket, so if std::enable_if
condition evaluates to true
then our second parameter becomes void*
with default value nullptr
:
template< class T, std::enable_if_t< std::is_integral_v< T > >* = nullptr >
void SwapInPlace( T& left, T& right )
{...}
template< class T, std::enable_if_t< std::is_floating_point_v< T > >* = nullptr >
void SwapInPlace( T& left, T& right )
{...}
So the question is: how deduction works in this 4 cases: why it fails in first case and succeds in three other?
You can remove everything about enable_if
in the question. It boils down to these three:
void
is an invalid anonymous template parameter:
template<class T, void>
void SwapInPlace( T& left, T& right );
A void*
as an anonymous template parameter with a default value is ok:
template<class T, void* = nullptr >
void SwapInPlace( T& left, T& right );
void
as return type is ok:
template<class T>
void SwapInPlace( T& left, T& right );
If you'd changed the first case to a valid anonymous template parameter without a default value, like an int
or void*
, it would compile:
template<class T, int>
void SwapInPlace( T& left, T& right );
... until you tried actually using it. You'd then get "couldn't deduce template parameter '<anonymous>
'" or similar.