Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloaded function template disambiguation with `std::enable_if` and non-deduced context

Consider the following code:

template <typename T>
struct dependent_type
{
    using type = T;
};

template <typename T>
auto foo(T) -> std::enable_if_t<std::is_same<T, int>{}>
{
    std::cout << "a\n"; 
}

template<typename T> 
void foo(typename dependent_type<T>::type) 
{
    std::cout << "b\n";
}
  • The first overload of foo can deduce T from its invocation.

  • The second overload of foo is a non-deduced context.

int main()
{    
    foo<int>( 1 );      // prints "b"
    foo<double>( 1.0 ); // prints "b"
    foo( 1 );           // prints "a"
}

Why does foo<int>( 1 ) print "b" and not "a"?

wandbox example

like image 709
Vittorio Romeo Avatar asked Jan 13 '17 12:01

Vittorio Romeo


1 Answers

Essentially the partial ordering rules say that the dependent_type overload is more specialized because of that non-deduced context.

The process for ordering template functions is based on transforming the template function types and performing template deduction on each in turn, once going from the first template (the one taking T) to the second (the one taking dependent_type), then from the second to the first.

The rules are far too complex to replicate here, but go read [temp.func.order] and the passages it links to if you want the gory details. Here's a quick simplification:

For each template parameter of the template function, make up a unique type and replace the parameter with that. The transformed types for this example are:

void foo(UniqueType); //ignoring the SFINAE for simplicity
void foo(typename dependent_type<UniqueType>::type); 

We then perform template deduction in two directions: once using the parameters of the first template as arguments to the second, and once using the parameters of the second as arguments to the first. This is akin to performing deduction on these function calls:

//performed against template <class T> void foo(typename dependent_type<T>::type);
foo(UniqueType{});                     

//performed against template <class T> void foo(T);        
foo(dependent_type<UniqueType>::type{});

In carrying out these deductions, we're trying to discern whether one overload is more specialized then the other. When we try the first one, deduction fails, since typename dependent_type<T>::type is a non-deduced context. For the second one, deduction succeeds because dependent_type<UniqueType>::type is just UniqueType, so T is deduced to UniqueType.

Since deduction failed going from the second template to the first, the second template is taken as being more specialized than the first. The final result is that the overload resolution prefers the second template for foo<int>(1).

like image 59
TartanLlama Avatar answered Sep 25 '22 21:09

TartanLlama