2016-09-06 5 views
8

У меня возникают проблемы с пониманием, почему следующие приводит к неоднозначному вызову:разрешения перегрузки с шаблоном параметрами

#include <iostream> 

// generic version f(X, Y) 
template <class X, class Y> 
void f(X x, Y y) { 
    std::cout << "generic" << std::endl; 
} 

// overload version 
template <class X> 
void f(X x, typename X::type y) { 
    std::cout << "overload" << std::endl; 
} 

struct MyClass { 
    using type = int; 
}; 

int main() { 
    f(MyClass(), int()); // Call to f is ambiguous 
} 

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

template <class X> 
void f(X x, int y) { 
    std::cout << "overload" << std::endl; 
} 

тогда вызов разрешен только в порядке, а это означает, что он должен делать с тем, что X :: тип является шаблон зависимым именем, но до сих пор не может работать почему он терпит неудачу. Буду признателен за любую оказанную помощь.

ответ

8

Во-первых, мы выбираем жизнеспособных кандидатов. Это:

void f(MyClass, int);     // with X=MyClass, Y=int 
void f(MyClass, typename MyClass::type); // with X=MyClass 

Эти кандидаты принимают одинаковые аргументы, поэтому имеют эквивалентные последовательности преобразования. Так что ни один из tiebreakers на основе тех, не применяются, поэтому мы падаем назад к последнему возможному тай-брейку в [over.match.best]:

Учитывая эти определения, жизнеспособная функция F1 определяется, чтобы быть лучше, чем функцией другая жизнеспособная функция F2, если для всех аргументов i, ICSi (F1) не хуже схемы преобразования, чем ICSi (F2), а затем [...] F1 и F2 являются специализированными шаблонами функций, а шаблон функции для F1 больше специализированный , чем шаблон для F2 в соответствии с правилами частичного заказа, описанными в 14.5.6.2.

Итак, мы пытаемся заказать два шаблона функций на основе правил частичного упорядочения, которые включают в себя синтез уникального типа для каждого параметра шаблона и попытку выполнить вычитание шаблона против каждой перегрузки. Но с ключом дополнительным соответствующим правилом из [temp.deduct.partial]:

Каждого типа номинированный выше из шаблона параметров и соответствующего типа из шаблона аргумента используются в качестве типов Р и А. Если конкретный P не содержит шаблонных параметров, участвующих в выводе аргумента шаблона, то P не используется для определения порядка.

Так что это значит. Сначала попробуем вывести общую версию из перегрузки. Мы выбираем синтетические типы UniqueX (для X) и UniqueX_type (для typename X::type) и видим, можем ли мы вызвать общую функцию. Это успешно (с X=UniqueX и Y=typename X::type).

Давайте попробуем инверсию. Мы выбираем UniqueX (для X) и UniqueY (для Y) и попытаемся выполнить вычет шаблонов. Для первой пары P/A это тривиально преуспевает. Но для второго аргумента X - это не выведенный контекст, который, по вашему мнению, будет означать, что вычет шаблона не удался. BUT согласно смещенной части цитаты, мы просто пропустим эту пару P/A для целей заказа. Итак, поскольку первая пара P/A преуспела, мы считаем, что весь процесс дедукции преуспел.

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


Когда вторая перегрузка изменяется на:

template <class X> void f(X, int); 

часть процесса, который изменяется в том, что вычет в настоящее время терпит неудачу в одном направлении. Мы можем вывести X=UniqueX, но вторая пара имеет параметр типа int и аргумент типа UniqueY, который не будет работать, поэтому это направление терпит неудачу. В обратном направлении мы можем вывести X=UniqueX и Y=int. Это делает эту перегрузку более специализированной, чем общая перегрузка, поэтому было бы желательно, чтобы последний тай-брейкер, о котором я упоминал ранее.


В качестве дополнения обратите внимание на то, что частичный порядок шаблонов является сложным. Рассмотрите:

template <class T> struct identity { using type = T; }; 

template <class T> void foo(T);        // #1 
template <class T> void foo(typename identity<T>::type); // #2 

template <class T> void bar(T, T);       // #3 
template <class T> void bar(T, typename identity<T>::type); // #4 

foo(0);  // calls #1, #2 isn't even viable 
foo<int>(0); // calls #2 
bar(0,0); // calls #3! we fail to deduce 3 from 4, but we succeed 
      // in deducing 4 from 3 because we ignore the second P/A pair! 
+0

Я собирался написать тот же ответ, но застрял в той части, которая заставила вас подчеркнуть **, но ** в ваших объяснениях. Я все еще не могу переварить вторую цитату в вашем ответе. Тем не менее, я считаю, что это должен быть принятый ответ. – Leon

+0

@Leon Первоначально выбрала неправильную цитату, моя вина. Новый - это тот, который я хотел включить. – Barry