Когда два функциональных шаблона считаются частично упорядоченными, а когда - неоднозначными?

Я полностью запутался после прочтения вопроса Как сделать эти параметры std::function однозначными? До сих пор я думал, что понимаю, что такое частичное упорядочение шаблонов функций, но после прочтения этого вопроса я записал три примера, чтобы проверить поведение компилятора, и полученные результаты мне трудно понять.

Пример № 1

template <class T>
void foo(T) {}

template <class T>
void foo(T&) {}

int main()
{
  int i;
  foo<int>(i); // error: call is ambiguous! 
}

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

Пример № 2

#include <iostream>

template <class T>
struct X {};

template <>
struct X<int> 
{
  X() {}
  X(X<int&> const&) {} // X<int> is constructible from X<int&>
  // note: this is not a copy constructor!
};

template <>
struct X<int&> 
{
  X() {}
  X(X<int> const&) {} // X<int&> is constructible from X<int>
  // note: this is not a copy constructor!
};

template <class T>
void bar(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void bar(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main()
{
  bar<int>(X<int>());   // calls void bar(X<T>) [with T = int]

  bar<int>(X<int&>());  // calls void bar(X<T&>) [with T = int]
}

Вопрос: если T& а также T неоднозначны в Примере №1, тогда почему здесь ни один вызов не является неоднозначным? X<int> конструктивно из X<int&>, так же как X<int&> конструктивно из X<int> благодаря предоставленным конструкторам. Это потому, что компилятор сгенерирован X<int>::X(X<int> const&) copy-constructor - лучшая последовательность преобразования, чем X<int>::X(X<int&> const&) (если так, что делает его лучше, обратите внимание, что аргументы передаются по значению), и поэтому порядок специализаций не имеет значения вообще?

Пример № 3

#include <iostream>

// note: a new type used in constructors!
template <class U>
struct F {};

template <class T>
struct X
{
  X() {}

  template <class U>
  X(F<U> const&) {}  // X<T> is constructible from any F<U>
  // note: it takes F type, not X!
};

template <class T>
void qux(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void qux(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main()
{
  qux<int>(F<int>());  // calls void qux(X<T&>) [with T = int]

  qux<int>(F<int&>()); // calls void qux(X<T&>) [with T = int]
}

Вопрос: Теперь этот сценарий похож на "соответствие лямбда" [](int){} против std::function<void(int&)> а также std::function<void(int)> " из вопроса связан. Почему в обоих вызовах выбирается более специализированный шаблон функции? Это потому, что последовательность преобразования одинакова, поэтому частичное упорядочение начинает иметь значение?

Все тесты выполнены на GCC 4.9.0 с -std=c++11 и без лишних флагов.

2 ответа

Решение

Разрешение перегрузки пытается найти лучшую функцию, подобную этой:

(1) [over.match.best] / 1:

Учитывая эти определения, жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2 если для всех аргументов я, ICSi(F1) не хуже последовательности преобразования, чем ICSi(F2), а потом
- для некоторого аргумента j, ICSj(F1) является лучшей последовательностью преобразования, чем ICSj(F2)или, если не так,
- контекст является инициализацией путем пользовательского преобразования (см. 8.5, 13.3.1.5 и 13.3.1.6) и стандартной последовательности преобразования из возвращаемого типа F1 для типа назначения (т. е. тип инициализируемого объекта) является лучшей последовательностью преобразования, чем стандартная последовательность преобразования из возвращаемого типа F2 к типу назначения.
[ Пример:

struct A {
  A();
  operator int();
  operator double();
} a;

int i = a;    // a.operator int() followed by no conversion is better 
              // than `a.operator double()`     
              // followed by a conversion to `int`
float x = a;  // ambiguous: both possibilities require conversions,
              // and neither is better than the other

- конец примера ] или, если не так,
- F1 - это не шаблонная функция, а F2 - специализация шаблона функции, или, если нет,
- F1 и F2 являются специализациями шаблонов функций, а шаблон функций для F1 более специализирован, чем шаблон для F2 в соответствии с правилами частичного упорядочения, описанными в 14.5.6.2.


Случай 1:

но не тот, кто принимает T& более специализированный, чем T?

В соответствии с разрешением перегрузки никакое преобразование не является лучшим (оба являются преобразованиями идентификаторов, которые являются точными совпадениями), и поскольку никакой другой пункт в (1) не применяется, выполняется частичное упорядочение. [temp.deduct.partial]/5 говорит, что ссылки заменяются типом, на который они ссылаются в целях частичного упорядочения:

Перед выполнением частичного упорядочения выполняются определенные преобразования для типов, используемых для частичного упорядочения:
- Если P является ссылочным типом, P заменяется указанным типом.
- Если A является ссылочным типом, A заменяется указанным типом.

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

Случай 2:

Частичный заказ здесь не нужен. Определяемое пользователем преобразование из X<int> в X<int&> имеет худший ранг, чем преобразование X<int> в X<int> - Последнему присваивается рейтинг "Точное совпадение" [over.ics.user]/4:

Преобразованию выражения типа класса в тот же тип класса присваивается рейтинг точного соответствия, […]

Таким образом, это, очевидно, лучшее преобразование, чем X<int> в X<int&>, который имеет конверсионный ранг. То же самое происходит и для X<int&> в X<int>,

Случай 3:

Третий случай похож на первый. X<int> а также X<int&> оба имеют шаблон конструктора, который может принимать произвольную специализацию F, (1) говорит нам, что, поскольку ни одна из последовательностей преобразования не лучше, чем другие в любом случае (на самом деле, они полностью идентичны), выбирается более специализированный шаблон.

template <class T> void bar(X<T>);  // #1

template <class T> void bar(X<T&>); // #2

// Call:
bar<int>( F<int>() );

Возвращаясь к [temp.deduct.partial], выполняется вывод типа. Уникальный тип, назовите его Unique, синтезируется для параметра шаблона каждого шаблона аргумента. Выполняются следующие процедуры с соответствующими результатами - обратите внимание, что при вызове с помощью F<int> как с F<int&> (и любая другая специализация F):

  1. шаблон № 1 в качестве шаблона параметра и шаблон № 2 в качестве шаблона аргумента, X<Unique&> выводится против X<T>, уступая T=Unique&, С другой стороны,
  2. шаблон № 2 в качестве шаблона параметра против шаблона № 1 в качестве шаблона аргумента, X<Unique> выводится против X<T&>, что приводит к неудаче удержания.

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

Пример 1:

Компилятор не может знать, хотите ли вы передать a по ссылке или по значению. Если вы специализируете свой шаблон с T * он легко узнает, потому что синтаксис вызова функции будет другим foo(&a),

Пример 2:

Здесь вы говорите компилятору, что вторая перегрузка qux занимает X<T &> поэтому он знает, что вы хотите построить этот объект с T &, Здесь нет двусмысленности. Но если вы сделаете это:

template <class T>
void qux(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void qux(X<T> &) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

Вы закончите с той же проблемой

Пример 3:

Та же проблема.

Я не знаю, если это очень ясно, так что если кто-то может улучшить мой ответ, это может быть полезно для автора

Другие вопросы по тегам