Компилирование следующего кода в VS2012 без проблем.
struct Foo
{
typedef int DummyType;
};
template<typename T>
int Bar(T* foo, typename T::DummyType* dummy_ = 0) { return 0; }
template<typename T>
int Bar(T* foo, ...) { return 1; }
template<typename T>
int Bar(typename T::DummyType* dummy_ = 0) { return 2; }
template<typename T>
int Bar(...) { return 3; }void fn()
{
Bar((Foo*)NULL);
Bar((int*)NULL);
Bar<Foo>();
Bar<int>();
}
Но попытка VS2013RC получила следующие ошибки. Это ошибка VS2013RC или проблема с самим кодом. Что говорится в стандарте о функции перегрузки соответствия, специализации функций шаблона и функций с переменным числом аргументов.
1>c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(25): error C2668: 'Bar' : ambiguous call to overloaded function
1> c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(15): could be 'int Bar<Foo>(T *,...)'
1> with
1> [
1> T=Foo
1> ]
1> c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(12): or 'int Bar<Foo>(T *,Foo::DummyType *)'
1> with
1> [
1> T=Foo
1> ]
1> while trying to match the argument list '(Foo *)'
1>c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(28): error C2668: 'Bar' : ambiguous call to overloaded function
1> c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(21): could be 'int Bar<Foo>(...)'
1> c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(18): or 'int Bar<Foo>(Foo::DummyType *)'
1> while trying to match the argument list '()'
Спасибо за любую помощь!
Спасибо за ответ!
Я только что сделал новый тест следующим образом:
struct Foo
{
typedef int DummyType;
};
// Bar0 #0
template<typename T>
static int Bar0(const T* foo, typename T::DummyType* dummy_) { return 0; }
// Bar0 #1
template<typename T>
static int Bar0(const T* foo, ...) { return 1; }template<typename T, typename U>
struct DummyType2 {};
// Bar1 #2
template<typename T>
static int Bar1(const T* foo, DummyType2<T, typename T::DummyType>* dummy_) { return 2; }
// Bar1 #3
template<typename T>
static int Bar1(const T* foo, ...) { return 3; }
void fn()
{
std::cout<<Bar0((Foo*)NULL, NULL)<<std::endl; // call 0 matches Bar0 #0
std::cout<<Bar1((Foo*)NULL, NULL)<<std::endl; // call 1 matches Bar1 #3
}
выход
0
3
По какой причине вызов 0 соответствует Bar0 # 0, но вызов 1 соответствует Bar1 # 3. Какие-нибудь правила из стандартных?
Нумерация этих четырех перегрузок для справки:
template<typename T>
int Bar(T*, typename T::DummyType* = 0); // #1
template<typename T>
int Bar(T*, ...); // #2
template<typename T>
int Bar(typename T::DummyType* = 0); // #3
template<typename T>
int Bar(...); // #4
Согласно [temp.deduct.type] / 5, typename T::DummyType
является не выводимым контекстом для T
, Т.е. параметр typename T::DummyType* dummy_
не может быть использован для вывода T
, Поэтому для первых двух звонков
Bar((Foo*)NULL);
Bar((int*)NULL);
T
может быть выведено для первых двух перегрузок, но не для вторых двух. Вот почему перегрузки № 3 и № 4 не являются жизнеспособными для этих вызовов. После этого вычета каждый случай T
в функции сигнатура будет заменена на выводимый тип. Это может привести к сбою замены, см. Вызов 2 ниже.
Для первого вызова возможны следующие две перегрузки:
/*substituted template*/
int Bar<Foo>(Foo*, Foo::DummyType* = 0); // #1
/*substituted template*/
int Bar<Foo>(Foo*, ...); // #2
Согласно [over.match.viable] / 2, аргументы по умолчанию игнорируются для разрешения перегрузки:
Во-первых, чтобы быть жизнеспособной функцией, функция-кандидат должна иметь достаточно параметров, чтобы их количество совпадало с аргументами в списке.
- Если есть м аргументы в списке, все функции-кандидаты имеют точно м параметры являются жизнеспособными.
- Функция-кандидат, имеющая меньше чем м Параметры являются жизнеспособными только в том случае, если в их списке параметров есть многоточие (8.3.5). В целях разрешения перегрузки любой аргумент, для которого нет соответствующего параметра, считается «соответствующим многоточию» (13.3.3.1.3).
- Функция-кандидат, имеющая более м Параметры являются жизнеспособными, только если (М + 1)-Параметр st имеет аргумент по умолчанию (8.3.6). В целях разрешения перегрузки список параметров обрезается справа, так что м параметры.
Итак, мы фактически сравниваем эти две подписи здесь:
/*substituted template*/
int Bar<Foo>(Foo*); // #1
/*substituted template*/
int Bar<Foo>(Foo*); // #2
Эти два ранга как точные совпадения и, следовательно, неоднозначны.
Для второго вызова произошла ошибка замещения первой перегрузки (см. Ниже), поэтому она не включена в список жизнеспособных функций. Жизнеспособна только одна перегрузка:
/*substituted template*/
int Bar<int>(int*); // #2
Ошибка замены:
Для второго звонка Bar((int*)NULL);
, замена T
за int
приводит к ошибке замены при первой перегрузке [temp.deduct] / 5:
Когда все аргументы шаблона были выведены или получены из аргументов шаблона по умолчанию, все использования параметров шаблона в списке параметров шаблона шаблона и типа функции заменяются соответствующими выведенными значениями или значениями аргументов по умолчанию. Если подстановка приводит к неверному типу, как описано выше, вывод типа завершается неудачно.
Недопустимый тип здесь int::DummyType
,
Для третьего и четвертого вызова жизнеспособны только последние две перегрузки (из-за количества аргументов). Остальное похоже на первые две перегрузки.
Третий звонок должен выбрать из перегрузок
/*substituted template*/
int Bar<Foo>(Foo::DummyType* = 0); // #3
/*substituted template*/
int Bar<Foo>(...); // #4
что неоднозначно, как первый звонок.
Для четвертого вызова третья перегрузка приводит к сбою замещения, и только четвертая перегрузка остается жизнеспособной (и выбирается однозначно).
Первый звонок: Bar0((Foo*)NULL, NULL)
Перегрузки:
// Bar0 #0
template<typename T>
static int Bar0(const T* foo, typename T::DummyType* dummy_);
// Bar0 #1
template<typename T>
static int Bar0(const T* foo, ...);
В Bar0
# 0, T
опять-таки в не выводимом контексте, поэтому для вывода используется только первый аргумент. Подставленные шаблонные подписи выглядят так:
// substituted template
static int Bar0<Foo>(const Foo*, Foo::DummyType*); // #0
// substituted template
static int Bar0<Foo>(const Foo* foo, ...); // #1
Определение NULL
теперь становится несколько актуальным:
[Conv.ptr] / 1Макрос
NULL
является определенной в реализации константой нулевого указателя C ++ в этом международном стандарте.
константа нулевого указателя является целочисленным константным выражением (5.19) prvalue целочисленного типа, который оценивается как ноль, или prvalue типа
std::nullptr_t
, Константа нулевого указателя может быть преобразована в тип указателя; результатом является значение нулевого указателя этого типа и отличается от любого другого значения указателя объекта или типа указателя на функцию.
Точный тип NULL
не указано (еще одна причина для использования nullptr
вместо!). Но мы знаем, что он конвертируется в Foo::DummyType*
, Это преобразование стандартное преобразование. согласование NULL
с многоточием это так называемый эллипсис; на самом деле это не преобразование, только с точки зрения разрешения перегрузки [over.ics.ellipsis] / 1:
Последовательность преобразования многоточия происходит, когда аргумент в вызове функции соответствует спецификации параметра многоточия вызываемой функции (см. 5.2.2).
Теперь две перегрузки жизнеспособны и должны быть ранжированы. К счастью, здесь все просто [over.ics.rank] / 2
стандартная последовательность преобразования представляет собой лучшую последовательность преобразования, чем определенная пользователем последовательность преобразования или последовательность преобразования с многоточием
Следовательно, последовательность преобразования тип NULL
в Foo::DummyType*
как требуется для перегрузки # 0, это лучшая последовательность преобразования по сравнению с последовательностью преобразования многоточия для сопоставления NULL
с ...
перегрузки № 2.
Второй звонок: Bar1((Foo*)NULL, NULL)
Перегрузки:
// Bar1 #2
template<typename T>
static int Bar1(const T*, DummyType2<T, typename T::DummyType>*);
// Bar1 #3
template<typename T>
static int Bar1(const T*, ...);
Здесь важной частью является T
в DummyType2<T, ..>
, это не в не выводимом контексте. Поэтому компилятор пытается вывести T
от первой а также Второй аргумент. Поскольку второй аргумент в вызове имеет некоторый неопределенный интеграл или std::nullptr_t
тип, вывод типа не удается из-за перегрузки Bar1
# 2. перегрузка Bar1
№ 3 остается жизнеспособным и однозначно выбран.
Однако если вы измените перегрузку Bar1
№ 2 для:
// Bar1 #2
template<typename T>
static int Bar1(const T*, DummyType2<int, typename T::DummyType>*);
затем T
выводится только из первого аргумента, и эта перегрузка является предпочтительной & выбран (по той же причине, что и при первом вызове последующего вопроса).
Вы также можете (вместо изменения перегрузки) изменить второй вызов на:
Bar1((Foo*)NULL, (DummyType2<Foo, int>*)NULL)
Сюда, T
может быть однозначно выведен Foo
и перегрузка Bar1
№ 2 выбран.
Других решений пока нет …