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

Я полностью смущен после прочтения вопроса Как сделать эти параметры 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 и без дополнительных флагов.

Ответ 1

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

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

Учитывая эти определения, жизнеспособная функция F1 определяется как лучше, чем другая жизнеспособная функция F2, если для всех аргументов i, 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> to 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 как шаблон шаблона и шаблон # 2 в качестве шаблона аргумента, X<Unique&> выводится на X<T>, что дает T=Unique&. С другой стороны,
  • шаблон # 2 в качестве шаблона параметра для шаблона # 1 в качестве шаблона аргумента, X<Unique> выводится на X<T&>, , что приводит к ошибке дедукции.

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

Ответ 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:

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

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