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

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

11

Решение

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

(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 вычет не удался. Поэтому и называется вторая, более специализированная специализация шаблонов функций.

7

Другие решения

Пример 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:

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

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

1

По вопросам рекламы [email protected]