Добавление изменений перегрузки, которые выбраны при перегрузке

Я пытаюсь понять, как правила выбора перегрузки приводят к следующему (неинтуитивному) поведению. Когда у меня есть следующие функции:

#include <iostream>

// Overload 1
template<class T>
void write(T data)
{
  std::cout << "Called write(T data)" << std::endl;
}

// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}

int main(int, char**)
{
  int j = 0;
  write(j);
  return 0;
}

void write(T data) перегрузка void write(T data) Перегрузка 1). Я думаю, что это имеет смысл для меня: кандидаты на выбор перегрузки являются void write<T>(T) T = int и void write<T,U>(T&) T = int, U = <>. И write(T) и write(T&) были бы одинаково специализированными, но Overload 2 имеет пустой пакет параметров, поэтому выбирается Overload 1. Однако, если добавить третью перегрузку:

#include <iostream>

// Overload 0
void write(const int& data)
{
  std::cout << "Called write(const int& data)" << std::endl;
}

// Overload 1
template<class T>
void write(T data)
{
  std::cout << "Called write(T data)" << std::endl;
}

// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}

int main(int, char**)
{
  int j = 0;
  write(j);
  return 0;
}

Затем внезапно появляется void write(T&& obj, U&&... objs) (перегрузка 2). Почему добавление перегрузки, которая не выбрана, изменяется, какая перегрузка действительно выбрана?

Если единственные кандидаты были void write<T,U>(T&) T = int, U = <> и void write(const int&) Я понимаю, почему выбрана void write<T,U>(T&), поэтому, возможно, что-то о добавлении дополнительной перегрузки предотвращает участие void write(T data) от выбора перегрузки? Если да, то почему?

Поскольку это, похоже, специфическое поведение компилятора, это наблюдалось на gcc 7.3.0.

Еще одно интересное поведение: если функции переупорядочиваются таким образом, что новая перегрузка помещается между исходными двумя (то есть Overload 1, Overload 0, Overload 2), то gcc отклоняет ее с call of overloaded 'write(int&) is ambiguous. Если функции переупорядочиваются так, что последняя перегрузка является последней (то есть перегрузка 1, затем перегрузка 2, затем перегрузка 0), то write(const int& data) выбрана.

Ответ 1

Я думаю, что это ошибка GCC:

Перегрузки:

  • Перегрузка 0: write(const int&)
  • Перегрузка 1: write(T) [T=int] → write(int)
  • Перегрузка 2: write(T&&,U&&...) [T=int&,U=[]] → write(int&)

Перегрузка 0 лучше, чем перегрузка 1, потому что перегрузка 0 не является специализированной функцией шаблонов.

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

Перегрузка 2 лучше, чем перегрузка 0, потому что cv-определитель типа параметра перегрузки 2 int& является подмножеством одной из перегрузок 0: const int&.

Таким образом, вызов неоднозначен, о чем сообщает Clang.


Чтобы упростить, наилучшая жизнеспособная функция здесь оценивается в 4 этапа при сравнении двух функций:

  1. Проверьте, какая из них является лучшей последовательностью конверсии (все конверсии - это преобразование идентичности здесь), тогда, если ранжирование конверсии одинаково:
  2. Убедитесь, что между двумя привязками привязки один лучше другого, тогда, если он не является ссылочной привязкой или два привязки не являются дифференцируемыми,
  3. Проверьте, является ли одна из функций специализированной специализацией, а другая - нет, тогда, если две из них являются специализациями шаблонов
  4. Проверьте, является ли одна из специализаций более специализированной, чем другая.