Reputation: 2271
Can someone please help me to understand why the following code
1) does not cause an error: redefinition of 'foo'
2) why it outputs T
and not A&
#include <type_traits>
#include <iostream>
class A{};
template< typename T >
void foo( T&& )
{
std::cout << "T" << std::endl;
}
template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::type > >
void foo( A_t&& )
{
std::cout << "A&" << std::endl;
}
template< typename A_t, std::enable_if_t< std::is_same<A_t, A>::type > >
void foo( A_t&& )
{
std::cout << "A" << std::endl;
}
int main()
{
A a;
foo( a );
}
Upvotes: 0
Views: 150
Reputation: 42929
The compiler prefers:
template< typename T >
void foo( T&& )
{
std::cout << "T" << std::endl;
}
Over:
template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::type>>
void foo( A_t&& )
{
std::cout << "A&" << std::endl;
}
and
template< typename A_t, std::enable_if_t< std::is_same<A_t, A>::type>>
void foo( A_t&& )
{
std::cout << "A" << std::endl;
}
Because the later two overloads introduce non-deduced context.
As already being stated in other answers, the application of std::enable_if
is not correct. In order to apply SFINAE you have to alter your code in one of the following ways:
template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::value>* = nullptr>
void foo( A_t&& )
{
std::cout << "A&" << std::endl;
}
template< typename A_t, std::enable_if_t< std::is_same<A_t, A>::value>* = nullptr>
void foo( A_t&& )
{
std::cout << "A" << std::endl;
}
template< typename A_t>
std::enable_if_t< std::is_same<A_t, A&>::value>
foo( A_t&& )
{
std::cout << "A&" << std::endl;
}
template< typename A_t>
std::enable_if_t< std::is_same<A_t, A>::value>
foo( A_t&& )
{
std::cout << "A" << std::endl;
}
Now once you've alter your code in one of the options illustrated above, you'll get an ambiguity compile error cause overload resolution cannot choose between template<typename A_t, std::enable_if_t<std::is_same<A_t, A&>::value>* = nullptr> void foo(A_t&&)
and template<typename T> void foo(T&&)
.
Once you get rid of the ambiguous call (i.e., get rid of template<typename T> void foo( T&& )
) the code will compile and run and template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::value>>
void foo( A_t&& )
is going to be choosen due to forwarding reference collapsing rules:
In your case a
is an lvalue
thus A_t
will be deduced to A&
and bulls-eye first overload is preferred due to SFINAE.
Upvotes: 1
Reputation: 120229
The answer to both questions is "because you are not using enable_if_t
correctly".
The pattern is
template< typename A_t,
typename = std::enable_if_t< std::is_same<A_t, A&>::value > >
Notes:
typename =
introduces an unnamed type template parameter. ::value
, not ::type
to pass to std::enable_if_t
. Once you use the correct pattern, you get a redefinition error for the second and third definitions (because you cannot overload on a default template parameter alone).
For the code as it stands now, the substitution always fails for the second template parameter, because std::enable_if_t
wants a non-type boolean argument and std::is_same<A_t, A&>::type
is a type. Since there are no valid instantiations of these templates, the behaviour is undefined.
Upvotes: 2
Reputation: 10939
You are using std::enable_if_t
incorrectly. Also the enabled definitions have to match the primary template. Therefore you could enable_if on the return type. Unsurprisingly the definition is ambiguous now.
#include <iostream>
#include <type_traits>
class A {};
template< typename T >
void foo( T&& )
{
std::cout << "T" << std::endl;
}
template< typename A_t >
std::enable_if_t< std::is_same<A_t, A&>::value >
foo( A_t&& )
{
std::cout << "A&" << std::endl;
}
template< typename A_t >
std::enable_if_t< std::is_same<A_t, A>::value >
foo( A_t&& )
{
std::cout << "A" << std::endl;
}
int main()
{
A a;
foo( a );
}
Why are you not just overloading foo
for A&&
and A
?
Upvotes: 2