Зависимый от аргумента поиск в шаблоне

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

Однако я обнаружил несоответствие между разными компиляторами, и мне интересно знать, что требует сам стандарт.

Рассмотрим следующий код:

#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?

3

Решение

Насколько перегрузка 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;
}
};
2

Другие решения

Других решений пока нет …