Поиск имен в шаблонах С++
У меня есть код С++, который больше не компилируется без опции -fpermissive.
Это код приличия, который я не могу предоставить, но я думаю, что мне удалось извлечь простой тестовый пример, демонстрирующий проблему. Вот результат из g++
template_eg.cpp: In instantiation of 'void Special_List<T>::do_other_stuff(T*) [with T = int]':
template_eg.cpp:27:35: required from here
template_eg.cpp:18:25: error: 'next' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
template_eg.cpp:18:25: note: declarations in dependent base 'List<int>' are not found by unqualified lookup
template_eg.cpp:18:25: note: use 'this->next' instead
Итак, вот код, который порождает проблему:
template<class T> class List
{
public:
void next(T*){
cout<<"Doing some stuff"<<endl;
}
};
template<class T> class Special_List: public List<T>
{
public:
void do_other_stuff(T* item){
next(item);
}
};
int main(int argc, char *argv[])
{
Special_List<int> b;
int test_int = 3;
b.do_other_stuff(&test_int);
}
Я не пытаюсь выяснить, как исправить код, чтобы он снова скомпилировался.
Это просто вопрос изменения следующего (элемента) этого- > следующего (элемента)
Я пытаюсь лучше понять, почему это изменение необходимо.
Я нашел объяснение на этой странице: http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html
Хотя это объяснение было полезным, у меня все еще есть некоторые вопросы. Не должен ли факт, что моя функция принимает T * (указатель на тип T), делает ее зависимой от аргумента шаблона.
В моей собственной формулировке, не должен ли компилятор (gcc 4.7) понять, что функция next() находится в списке базового класса?
Почему это необходимо перед этим-перед этим вызовом?
Я заметил, что clang 3.1 демонстрирует такое же поведение, поэтому я предполагаю, что в стандарте С++ есть какое-то требование, требующее такого поведения. Может ли кто-нибудь объяснить это?
Ответы
Ответ 1
Проблема в том, что шаблоны обрабатываются за два прохода (в соответствии со стандартом, VS делает иначе). В первом проходе, перед подстановкой типа, все, что не зависит от аргументов шаблона, проверяется и проверяется. Зависимые имена затем оставляются для разрешения во втором проходе после замены типа.
Теперь в первом проходе нет ничего, что указывало бы, что next
зависит от аргументов шаблона и, следовательно, его нужно разрешить перед заменой типа. Теперь, поскольку базовый тип шаблонизирован в аргументе шаблона вашего текущего шаблона, компилятор не может его изучить (он может быть специализированным для некоторых типов и не зная, какой тип T
мы создаем с шаблоном, мы не можем знать какую специализацию использовать, т.е. база зависит от T
, и мы проверяем, прежде чем знать T
).
Трюк добавления this->
превращает next
в зависимое имя, а это, в свою очередь, означает, что поиск задерживается до второго прохода, где T
известен, и поскольку T
известен, List<T>
> также известен и может быть просмотрен.
РЕДАКТИРОВАНИЕ. Одна важная деталь, отсутствующая в формулировке ответа выше, заключается в том, что поиск второй фазы (после подстановки типов) добавит только функции, найденные во время поиска зависимого от аргумента. То есть, если next
была свободной функцией в пространстве имен, связанном с T
, она была бы найдена, но она является членом на основе, которая не видна для ADL на T
.
Ответ 2
Вам нужно написать this->
как:
this->next(item);
Здесь this->
требуется часть, потому что next()
является наследуемым членом из базы шаблонов, и если вы внимательно прочитали сообщение об ошибке, там также предлагается:
template_eg.cpp: 18: 25: note: объявления в зависимой базе 'List<int>'
не найдены неквалифицированным поиском
template_eg.cpp: 18: 25: note: использовать 'this->next'
вместо
Прочитайте эту статью, которая объяснила поиск двухфазного имени в С++:
Ответ 3
Если ваш базовый класс является экземпляром шаблона, тогда нет способа узнать, что next
ссылается на имя в базовом классе - в конце концов, имя не должно даже существовать (подумайте об специализациях)! Таким образом, вы должны утверждать компилятору, что next
на самом деле является членом класса, говоря this->
или List<T>::next
, или добавив using List<T>::next;
к шаблону производного класса.