Порядок объявления шаблона функции влияет на видимость (иногда) - PullRequest
3 голосов
/ 30 апреля 2009

Я пытаюсь создать функцию:

template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}

, где поведение меняется в зависимости от типа переданного p. В частности, вызываемая версия getClassName должна зависеть от типа p. В следующем примере я могу успешно позвонить:

doIt<myClass1>( myClass1*& )
doIt<myClass1<int> >( myClass1*& )
doIt<myClass2>( myClass2*& )
doIt<myClass2<int> >( myClass2*& )

но не получается, когда я звоню:

doIt< std::vector<int, std::allocator<int> > >( std::vector<int, std::allocator<int>>*& )

с ошибкой:

a.cxx: In function ‘void doIt(T*&) [with T = std::vector<int, std::allocator<int> >]’:
ba.cxx:87:   instantiated from here
a.cxx:33: error: invalid initialization of reference of type ‘MyClass1&’ from expression of type ‘std::vector<int, std::allocator<int> >’
a.cxx:16: error: in passing argument 1 of ‘const char* getClassName(MyClass1&)’

(gcc 4.2.4).

Если я перенесу объявление:

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

перед doIt - затем компилируется. Таким образом,

  • Почему требуется, чтобы getClassName( std::vector<T,A>& ) появлялось до doIt, а не getClassName( MyClass2T<T>& )
  • Что я могу сделать, чтобы doIt не зависел от std::vector? (Я хочу иметь возможность разместить doIt в своем собственном заголовке и не должен знать о std::vector или какой-либо из специализаций, которые будут определены пользователем).

.

#include <stdio.h>
#include <assert.h>
#include <vector>

//template<typename T>
//char const* getClassName( T& );

//template<typename T, typename A>
////char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

#if 1
// ---------  MyClass2
struct MyClass1
{};

char const* getClassName( MyClass1& ) { printf("MyClass1\n"); return NULL; }

// ---------  MyClass1T
template< typename T>
struct MyClass1T
{};

template<typename T>
char const* getClassName( MyClass1T<T>& ) { printf("MyClass1T<T>\n"); return NULL; }
#endif


template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}


// ---------  MyClass2
struct MyClass2
{};


// declared after doIt, OK.
char const* getClassName( MyClass2& ) { printf("MyClass2\n"); return NULL; }

// ---------  MyClass2T
template< typename T>
struct MyClass2T
{};

// declared after doIt, OK.
template<typename T>
char const* getClassName( MyClass2T<T>& ) { printf("MyClass2T<T>\n"); return NULL; }

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }



void test()
{
#if 1
   MyClass1 mc1;
   MyClass1* mc1p = &mc1;
   doIt( mc1p );

   MyClass2 mc2;
   MyClass2* mc2p = &mc2;
   doIt( mc2p );

   MyClass1T<int> mc1t;
   MyClass1T<int>* mc1tp = &mc1t;
   doIt( mc1tp );

   MyClass2T<int> mc2t;
   MyClass2T<int>* mc2tp = &mc2t;
   doIt( mc2tp );

   // Nested templates are OK.
   MyClass2T<MyClass1> mc2t2;
   MyClass2T<MyClass1>* mc2tp2 = &mc2t2;
   doIt( mc2tp2 );
#endif

#if 1
   std::vector<int> v;
   std::vector<int>* vp = &v;
   doIt( vp );                   // FAIL!
#endif
}

Ответы [ 2 ]

4 голосов
/ 30 апреля 2009

Причиной сбоя является то, что при создании экземпляра не выполняется поиск безоговорочного имени для функций (но только ADL - Аргумент-зависимый поиск). Контекст создания экземпляра (взят из 14.6.4.1/6 стандарта C ++):

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

Точка создания всех тех специализаций шаблонов, которые вы вызвали в этом случае, находится сразу после определения test (читай 14.6.4.1/1). Итак, все объявленные вами функции видны в вашей функции test с использованием неквалифицированного поиска, но поиск для них на самом деле отличается для вызовов функций:

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

  • Имена из контекста определения шаблона рассматриваются как обычным поиском, так и ADL.
  • Имена из контекста экземпляра рассматриваются только для ADL.

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

Зависимый от аргумента поиск (ADL)

Для аргумента типа std::vector<T> ADL ищет функции в пространстве имен std и пространстве имен T. Помещение функции getClassName в пространство имен std будет работать для этого (но это не разрешено Стандартом, потому что это приводит к неопределенному поведению - это должно быть сделано только в качестве последнего средства).

Чтобы увидеть эффекты ADL, попробуйте позвонить doIt с вектором MyClass2 вместо int. С тех пор T = MyClass2, ADL будет искать в пространстве имен MyClass2 подходящую функцию, принимающую std::vector<MyClass2>, и преуспеет - в отличие от того, когда вы используете int, которая будет смотреть только на std.

Для других вызовов функций все их соответствующие объявления также найдены, поскольку все они объявлены в глобальном пространстве имен, в котором также определены типы аргументов вызовов функций (MyClass1, MyClass2 и т. Д.).

FAQ по C ++ - это хорошо, но он не углубляется в шаблоны (в нем не упоминается ADL). Существует специальный шаблон faq , который обрабатывает некоторые из наиболее сложных ловушек.


Остерегайтесь неопределенного поведения

Обратите внимание, что многие компиляторы будут принимать код, даже когда вы поместите эту декларацию, которую я показал после функции test (вместо нее). Но, как сказано в приведенной выше стандартной цитате, объявление не будет частью контекста создания экземпляра, и следует соблюдать правило, найденное в 14.6.4.2/1:

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

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

Надеюсь, это поможет.

4 голосов
/ 30 апреля 2009

Почему требуется, чтобы getClassName (std :: vector &) появлялся перед doIt, а не getClassName (MyClass2T &)

Объявление в области требуется для любой функции. Когда вы создаете экземпляр функции шаблона с vector<int>, он ожидает, что функция с подписью getClassName(vector<int>&) будет присутствовать (как минимум, прототип) для успешной компиляции.

Что я могу сделать, чтобы сделать doIt независимым от std :: vector? (Я хочу иметь возможность размещать doIt в своем собственном заголовке и не должен знать о std :: vector или какой-либо из специализаций, которые будут определены пользователем)

Прочтите FAQ по шаблонам . Попробуйте поместить прототип всех зависимых шаблонных функций doIt перед первым созданием doIt.

...