Порядок объявления шаблона функции влияет на видимость (иногда)

StackOverflow https://stackoverflow.com/questions/804217

Вопрос

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

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&)’

(ОКК 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
}
Это было полезно?

Решение

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

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

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

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

Другие советы

Причина сбоя заключается в том, что при создании экземпляра не происходит поиск неполных имен для функций (а только 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 и т. д).

Часто задаваемые вопросы по C++ хороши, но они не углубляются в шаблоны (не нашел в нем упоминаний об ADL).Есть специальный шаблон часто задаваемых вопросов это решает некоторые из наиболее сложных ловушек.


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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top