abraham_hilbert
abraham_hilbert

Reputation: 2271

Why does enable_if not behave as expected?

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

Answers (3)

Dimitrios Bouzas
Dimitrios Bouzas

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:

Option 1:

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; 
}

Option 2:

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:

  • A& & becomes A&
  • A& && becomes A&
  • A&& & becomes A&
  • A&& && becomes A&&

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

n. m. could be an AI
n. m. could be an AI

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:

  1. typename = introduces an unnamed type template parameter.
  2. You need ::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

Henri Menke
Henri Menke

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

Related Questions