Подтвердить что ты не робот

SFINAE работает по-разному в случаях типовых и нетиповых параметров шаблона

Почему этот код работает:

template<
    typename T, 
    std::enable_if_t<std::is_same<T, int>::value, T>* = nullptr>
void Add(T) {}

template<
    typename T, 
    std::enable_if_t<!std::is_same<T, int>::value, T>* = nullptr>
void Add(T) {}

и может правильно различать эти два вызова:

Add(1);
Add(1.0);

в то время как следующий код, если скомпилирован, приводит к переопределению ошибки Add()?

template<
    typename T, 
    typename = typename std::enable_if<std::is_same<T, int>::value, T>::type>
void Add(T) {}

template<
    typename T, 
    typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type>
void Add(T) {}

Итак, если параметр шаблона является типом, тогда мы переопределяем функцию, если она не является типом, тогда все в порядке.

4b9b3361

Ответ 1

SFINAE - это замена. Итак, заменим!

template<
  typename T, 
  std::enable_if_t<std::is_same<T, int>::value, T>* = nullptr>
void Add(T) {}

template<
  typename T, 
  std::enable_if_t<!std::is_same<T, int>::value, T>* = nullptr>
void Add(T) {}

становится:

template<
  class T=int, 
  int* = nullptr>
void Add(int) {}

template<
  class T=int, 
  Substitution failure* = nullptr>
void Add(int) {

template<
  class T=double, 
  Substitution failure* = nullptr>
void Add(double) {}

template<
  class T=double
  double* = nullptr>
void Add(double) {}

Удалите ошибки, которые мы получаем:

template<
  class T=int, 
  int* = nullptr>
void Add(int) {}
template<
  class T=double
  double* = nullptr>
void Add(double) {}

Теперь удалите значения параметров шаблона:

template<
  class T, 
  int*>
void Add(T) {}
template<
  class T
  double*>
void Add(T) {}

Это разные шаблоны.

Теперь тот, который испортился:

template<
  typename T, 
  typename = typename std::enable_if<std::is_same<T, int>::value, T>::type>
void Add(T) {}

template<
  typename T, 
  typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type>
void Add(T) {}

становится:

template<
  typename T=int, 
  typename =int>
void Add(int) {}

template<
  typename int, 
  typename = Substitution failure >
void Add(int) {}

template<
  typename T=double, 
  typename = Substitution failure >
void Add(double) {}

template<
  typename T=double, 
  typename = double>
void Add(double) {}

Удалите ошибки:

template<
  typename T=int, 
  typename =int>
void Add(int) {}
template<
  typename T=double, 
  typename = double>
void Add(double) {}

И теперь значения параметров шаблона:

template<
  typename T, 
  typename>
void Add(T) {}
template<
  typename T, 
  typename>
void Add(T) {}

Это одна и та же подпись шаблона. И это недопустимо, сгенерирована ошибка.

Почему такое правило? Помимо объема этого ответа. Я просто демонстрирую, как эти два случая отличаются друг от друга, и утверждая, что стандарт рассматривает их по-разному.

При использовании параметра шаблона, отличного от типа, указанного выше, вы изменяете подпись шаблона не только значения параметров шаблона. Когда вы используете параметр шаблона типа, как указано выше, вы изменяете только значения параметров шаблона.

Ответ 2

Здесь проблема заключается в том, что подпись шаблона для add() одинаков: шаблон функции с двумя типами параметров.

Итак, когда вы пишете:

template<
    typename T, 
    typename = std::enable_if_t<std::is_same<T, int>::value, T>>
void Add(T) {}

Это хорошо, но когда вы пишете:

template<
    typename T, 
    typename = std::enable_if_t<!std::is_same<T, int>::value, T>>
void Add(T) {}

Вы переопределяете первый шаблон add(), только на этот раз вы укажете другой тип по умолчанию для второго параметра шаблона: в конце вы определили перегрузку для add() с той же самой сигнатурой, а значит и с ошибкой.

Если вы хотите предложить несколько вариантов реализации, например, ваш вопрос, вы должны использовать std::enable_if_t как возвращаемый параметр вашего шаблона или использовать его так же, как ваш первый пример. Таким образом, ваш исходный код будет выглядеть следующим образом:

template<typename T>
std::enable_if_t<std::is_same<T, int>::value> Add(T) {}
template<typename T>
std::enable_if_t<!std::is_same<T, int>::value> Add(T) {}

Рабочий пример Coliru

В приведенном выше коде, если T == int, вторая подпись становится недействительной и запускает SFINAE.

NB: Предположим, что вам нужны N реализаций. Вы можете использовать тот же трюк, что и выше, но вам нужно убедиться, что только один логический из N является истинным, а N-1, который остается, является ложным, иначе вы получите точно такую ​​же ошибку!

Ответ 3

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

Ответ 4

SFINAE не распространяется на значения по умолчанию для любых типов и значений. В этом методе используются только типы аргументов функции и результат.

Ответ 5

Я попытаюсь сначала привести пример без использования шаблонов, но с аргументами по умолчанию. Следующий пример сопоставим с тем, почему ваш второй пример неудачен, хотя он не указывает на внутреннюю работу разрешения перегрузки шаблона.

У вас есть две функции, объявленные как таковые:

void foo(int _arg1, int _arg2 = 3);

И

void foo(int _arg1, int _arg2 = 4);

Надеюсь, вы поймете, что это не скомпилируется, их никогда не будет способ различать два вызова foo с аргументом по умолчанию. Это совершенно неоднозначно, как компилятор когда-либо узнает, какой выбор по умолчанию выбрать? Вы можете задаться вопросом, почему я использовал этот пример, ведь шаблон шаблона в первом примере не выводит разные типы? Короткий ответ на это - нет, и это потому, что "подпись" двух шаблонов в вашем втором примере:

template<
    typename T, 
    typename = typename std::enable_if<std::is_same<T, int>::value, T>::type>
void Add(T) {}

template<
    typename T, 
    typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type>
void Add(T) {}

... имеют ту же самую подпись, что:

template<typename T,typename>
void Add(T);

И (соответственно)

template <typename T, typename>
void Add(T); 

Теперь вы должны начать понимать сходство между примером, который я дал с не-шаблонами, и примером, который вы предоставили, что не удалось.