Переустановленное значение шаблона функции с помощью `std:: enable_if` и не выводимого контекста

Рассмотрим следующий код:

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";
}
  • Первая перегрузка foo может вывести T из ее вызова.

  • Вторая перегрузка foo представляет собой не выводимый контекст.

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

Почему foo<int>( 1 ) печатает "b", а не "a"?

пример wandbox

Ответы

Ответ 1

По существу, правила частичного упорядочения говорят о том, что перегрузка dependent_type более специализирована из-за этого не выводимого контекста.

Процесс упорядочения функций шаблонов основан на преобразовании типов функций шаблона и выполнении каждого из них поэтапного шаблона, переходя от первого шаблона (тот, который принимает T) ко второму (тот, который принимает dependent_type), затем от второго к первому.

Правила слишком сложны для репликации здесь, но прочитайте [temp.func.order] и проходы, на которые он ссылается, если вы хотите узнать подробности, Здесь быстрое упрощение:

Для каждого параметра шаблона функции шаблона создайте уникальный тип и замените его параметром. Преобразованные типы для этого примера:

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

Затем мы выполняем вывод шаблона в двух направлениях: один раз используя параметры первого шаблона в качестве аргументов для второго и один раз используя параметры второго в качестве аргументов для первого. Это похоже на выполнение вычета этих вызовов функций:

//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{});

При выполнении этих вычетов мы пытаемся определить, является ли одна перегрузка более специализированной, а другая. Когда мы пытаемся выполнить первый, вывод не выполняется, поскольку typename dependent_type<T>::type - это не выводимый контекст. Для второго вывод выводится успешно, потому что dependent_type<UniqueType>::type является просто UniqueType, поэтому T выводится UniqueType.

Поскольку вывод не прошел от второго шаблона к первому, второй шаблон считается более специализированным, чем первый. Конечным результатом является то, что разрешение перегрузки предпочитает второй шаблон для foo<int>(1).