неявное преобразование в специализации шаблона - PullRequest
1 голос
/ 29 апреля 2019

В следующем коде у меня есть функция шаблона не-члена и ее полная специализация для типа int.

#include <iostream>

template <typename U>
void f(const U& x, const U& y)
{
  std::cout << "generic " << x << " " << y << std::endl;
}

template <>
void f(const int& x, const int& y)
{
  std::cout << "specialization int " << x << " " << y << std::endl;
}

int main()
{
  int a = 1;
  f(a, a);
  f('a', 'a');

  f('a', 1); // Compiler error
  // f<int>('a', 1); // This compiles

  return 0;
}

Хотя на языке доступно неявное преобразование из char в int, компилятор (g ++ 7.3.0 и clang 6.0.1) не компилирует код, выдавая ошибку

error: no matching function for call to ‘f(char, int)’
deduced conflicting types for parameter ‘const U’ (‘char’ and ‘int’)

Хотя понятно, почему вывод шаблона не будет работать, мне неясно, почему компилятор не учитывает неявное преобразование после того, как он отбросил общий шаблон. Например, если я явно создаю экземпляр f с U=int, раскомментируя соответствующую строку в коде как

f<int>('a', 1);

затем код компилируется и правильно выдает вывод

specialization int 1 1
generic a a
specialization int 97 1

Если вместо этого я дополню код перегрузкой из f вместо специализации шаблона как

#include <iostream>

template <typename U>
void f(const U& x, const U& y)
{
  std::cout << "generic " << x << " " << y << std::endl;
}

void f(const int& x, const int& y)
{
    std::cout << "overload int " << x << " " << y << std::endl;
}

int main()
{
  int a = 1;
  f(a, a);
  f('a', 'a');

  f('a', 1);

  return 0;
}

Затем код компилируется и выдает ожидаемый результат

overload int 1 1
generic a a
overload int 97 1

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

Ответы [ 2 ]

4 голосов
/ 29 апреля 2019

Когда компилятор видит это:

f('a', 1);

Невозможно определить тип, поскольку у него есть два варианта:

f(const char &, const char &);
f(const int &, const int &);

Поскольку ваш шаблон имеет общий тип для обоих аргументов.

Оба варианта одинаково действительны, и нет разумного правила для устранения этой двусмысленности. Таким образом, компилятор должен сообщить об ошибке, чтобы избежать нежелательного поведения. Обратите внимание, что автоматическое преобразование типов не влияет на эту проблему, также ваша специализация шаблона также не может помочь решить эту проблему.

Та же проблема будет поднята для std::max.

Теперь вопрос: уверены ли вы, что второй аргумент важнее и должен влиять на тип аргумента шаблона? Если да, то вы можете принудительно игнорировать тип первого аргумента (отказ от ответственности: это необычно и неожиданно, так что это может привести к ошибкам для будущих разработчиков кода).

template <typename T>
struct Identity {
    using type = T;
};
// note C++20 introduces std::type_identity

template<typename T>
void f(const typename Identity<T>::type& x, const T& y)
{
  std::cout << "generic " << x << " " << y << std::endl;
}

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

Теперь это

f('a', 1);

скомпилируется, так как второй аргумент приведет к T=int, и ожидается, что тип первого аргумента будет таким же, как и для второго аргумента. Теперь можно выполнить тихое преобразование.

Живой пример .

1 голос
/ 29 апреля 2019

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

Поскольку специализация шаблона - это точно специализация.

Итак,когда у вас есть функция шаблона и специализация шаблона, и вы пишете

f(x, y);

, компилятор выводит типы для x и y.

Если и только если выводимые типы одинаковы, рассмотрите функцию шаблона, и если и только если тип равен int (для обоих аргументов), выберите специализацию.

Когда вы вызываете

f<someType>(x, y);

, вы говорите компилятору: «игнорируйте вывод типа и вызывайте функцию шаблона f(), навязывая someType как T».

В этомcase, пишущий

f<int>('a', 1);

, компилятор выбирает специализацию шаблона и конвертирует a в int.

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

...