Двухфазный поиск имени: PODs против пользовательских типов
При компиляции и запуске кода
#include <iostream>
struct A { A(int){} };
void foo( int ) { std::cout << "foo(int)" << std::endl; }
template< typename T >
struct S {
void bar() { foo( 5 ); }
void foobar() { T t = 5; foo(t); }
};
inline void foo( A ) { std::cout << "foo(A)" << std::endl; }
inline void foo( double ) { std::cout << "foo(double)" << std::endl; }
int main(int argc, char* argv[])
{
S<double> s0;
s0.bar();
s0.foobar();
std::cout << '\n';
S<A> s1;
s1.bar();
s1.foobar();
}
Я получаю вывод (используя g++ 4.8, clang++ 3.2 или icpc 13.1)
foo(int)
foo(int)
foo(int)
foo(A)
Хотя последние две строки имеют смысл для меня, учитывая правила двухфазного поиска, я бы ожидал foo(int) foo(double)
для первых двух строк.
Похоже, что в этом случае для foobar()
вызов foo()
ищется перед созданием экземпляра, что не должно быть возможным. Есть намеки?
2 ответа
Поскольку T
это параметр шаблона, выражение t
зависит от типа и, следовательно, foo
является зависимым именем в вызове функции foo(t)
, [temp.dep.candidate] 14.6.4.2/1 говорит:
Для вызова функции, который зависит от параметра шаблона, функции-кандидаты находятся с использованием обычных правил поиска (3.4.1, 3.4.2, 3.4.3), за исключением того, что:
Для части поиска, использующей поиск без определения имени (3.4.1) или поиск по квалифицированному имени (3.4.3), обнаруживаются только объявления функций из контекста определения шаблона.
Для части поиска, использующей связанные пространства имен (3.4.2), найдены только объявления функций, найденные либо в контексте определения шаблона, либо в контексте создания шаблона.
Если имя функции является безусловным идентификатором, и вызов был бы некорректным или нашел бы лучшее соответствие, если бы поиск в связанных пространствах имен учитывал все объявления функций с внешней связью, введенной в эти пространства имен во всех единицах перевода, а не только с учетом эти объявления, найденные в контекстах определения шаблона и создания экземпляра шаблона, ведут к неопределенному поведению программы.
При создании экземпляра S<double>::foobar
, T
очевидно double
и не имеет связанных пространств имен. Так что единственные декларации foo
будут найдены те из контекста определения шаблона (void foo(int)
), как описано в первом пункте.
При создании экземпляра S<A>::foobar
, T
является A
, Декларации foo
как в контексте определения
void foo(int)
и из
A
Связанное пространство имен (глобальное пространство имен) найдено:inline void foo( A ) { std::cout << "foo(A)" << std::endl; }
inline void foo( double ) { std::cout << "foo(double)" << std::endl; }
очевидно void foo(A)
это лучший матч.
Просто определите foo (double) перед S:
#include <iostream>
struct A { A(int){} };
void foo( int ) { std::cout << "foo(int)" << std::endl; }
inline void foo( double ) { std::cout << "foo(double)" << std::endl; } //here
inline void foo( A ) { std::cout << "foo(A)" << std::endl; }
template< typename T >
struct S {
void bar() { foo( 5 ); }
void foobar() { T t = 5; foo(t); }
};
int main(int argc, char* argv[])
{
S<double> s0;
s0.bar();
s0.foobar();
std::cout << '\n';
S<A> s1;
s1.bar();
s1.foobar();
}
выход:
foo (int) foo (double)
foo (int) foo(A)
РЕДАКТИРОВАТЬ: Таким образом, S:: foo является независимым именем и разрешается, когда шаблон определен, а S:: foobar является зависимым именем и разрешается, когда создается экземпляр шаблона.
Вот почему s1.foobar
может напечатать A (потому что foo(A) определен в этой точке)