Я запутался в поиске имени функции в контексте шаблона. Я знаю, что компилятор задерживает поиск зависимого от аргумента идентификатора в шаблонном коде, пока не будет создан экземпляр шаблона. Это означает, что вы можете иногда иметь синтаксические ошибки или вызывать несуществующие функции в шаблонном коде, и компилятор не будет жаловаться, если вы на самом деле не создадите экземпляр шаблона.
Однако я обнаружил несоответствие между разными компиляторами, и мне интересно знать, что требует сам стандарт.
Рассмотрим следующий код:
#include <iostream>
class Foo
{
public:
template <class T>
void bar(T v)
{
do_something(v);
}
};
void do_something(std::string s)
{
std::cout << "do_something(std::string)" << std::endl;
}
void do_something(int x)
{
std::cout << "do_something(int)" << std::endl;
}
int main()
{
Foo f;
f.bar("abc");
f.bar(123);
}
Обратите внимание, что функция-член шаблона Foo::bar
вызывает не зависящая от аргумента глобальная функция называется do_something
, который еще даже не было объявлено.
Тем не менее, GCC 4.6.3 с радостью скомпилирует вышеуказанную программу. При запуске вывод:
do_something(std::string)
do_something(int)
Таким образом, похоже, что компилятор задержал поиск идентификатора до тех пор, пока не был создан экземпляр шаблона, и в этот момент он смог найти do_something
,
В отличие от GCC 4.7.2 будет не скомпилируйте вышеуказанную программу. Выдает следующую ошибку:
test.cc: In instantiation of ‘void Foo::bar(T) [with T = const char*]’:
test.cc:27:13: required from here
test.cc:10:3: error: ‘do_something’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
test.cc:19:6: note: ‘void do_something(int)’ declared here, later in the translation unit
Итак, GCC 4.7.2 осознает, что do_something
позже объявлено, но отказывается компилировать программу, потому что do_something
не зависит от аргумента.
Итак, я предполагаю, что GCC 4.7.2, вероятно, здесь правильный, а GCC 4.6.3 — неправильный. Итак, предположительно, мне нужно объявить do_something
до Foo::bar
определено. Проблема в том, что я хочу разрешить пользователям моего класса Foo
расширить поведение Foo::bar
путем реализации своих собственных перегрузок do_something
, Мне нужно написать что-то вроде:
#include <iostream>
template <class T>
void do_something(T v)
{
std::cout << "do_something(T)" << std::endl;
}
class Foo
{
public:
template <class T>
void bar(T v)
{
do_something(v);
}
};
void do_something(int x)
{
std::cout << "do_something(int)" << std::endl;
}
int main()
{
Foo f;
f.bar("abc");
f.bar(123);
}
Проблема здесь в том, что перегрузки do_something
не видны изнутри Foo::bar
и таким образом никогда не звонил. Так что даже если я позвоню do_something(int)
позвонит do_something(T)
а не перегрузка для int
, Таким образом, как с GCC 4.6.3, так и с GCC 4.7.2, вышеприведенные результаты программы:
do_something(T)
do_something(T)
Итак, каковы некоторые решения здесь? Как я могу позволить пользователям расширяться Foo::bar
путем реализации своих собственных перегрузок do_something
?
Насколько перегрузка do_something
выходит, нужно специализировать свой оригинальный шаблон:
template<>
void do_something<int>(int x) {
std::cout << "do_something(int)" << std::endl;
}
Редактировать : Как @MatthieuM. Как указывалось выше, специализация шаблона функции может привести к странным результатам, если вам также необходимо перегрузить функцию (и в какой-то момент вам, вероятно, потребуется, поскольку шаблоны функций не могут быть частично специализированы). Смотрите ссылку Матье на статью Херба Саттера Почему бы не специализировать шаблоны функций? для полного объяснения.
Вместо этого рекомендуется использовать статическую функцию, заключенную в структуру, которая допускает частичную специализацию и устраняет проблему разрешения имен, которая идет с перегруженными шаблонами функций.
template<typename T>
struct DoSomething {
static void do_something(T v) {
std::cout << "do_something(T)" << std::endl;
}
};
struct Foo
{
template <class T>
void bar(T v) {
DoSomething<T>::do_something(v);
}
};
// Now you can specialize safely
template<>
struct DoSomething<int> {
static void do_something(int v) {
std::cout << "do_something(int)" << std::endl;
}
};
Других решений пока нет …