Двухфазный поиск имени: 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) определен в этой точке)

Другие вопросы по тегам