Язык С++ не перестаёт удивлять меня. Изучаю его давно, тем не менее, периодически обнаруживаются неизведанные уголки. Есть ощущение, что C++ так же неисчерпаем, как и атом.
Как известно, внутри классов можно объявлять шаблонные статические методы:
struct Foo {
template <class T>
static int method() { return 0; };
static int method() { return 0; };
};
Теперь можно написать шаблонную функцию f, которая для заданного параметра T зовет соответствующий method<T> из класса Foo:
template<class T>
int f() { return Foo::method<T>(); }
int f() { return Foo::method<T>(); }
Попробуем теперь параметризовать эту функцию еще классом S, из которого зовется метод, и подставить Foo вместо S:
template<class S, class T>
int f() { return S::method<T>(); }
...
int i = f<Foo, int>();
int f() { return S::method<T>(); }
...
int i = f<Foo, int>();
Что же мы видим? Наш любимый g++ выдает ошибку:
test.cpp: error: expected primary-expression before '>' token
test.cpp: error: expected primary-expression before ')' token
Оказывается, нельзя вызывать шаблонный статический метод с явным указанием типа, когда класс-владелец метода сам является шаблонным параметром.
Собственно, можно даже в функции f отказаться от параметра T, положив его равным, скажем, int:
template<class S>
int f() { return S::method<int>(); }
int f() { return S::method<int>(); }
Получаем то же самое сообщение от компилятора: ему не нравятся именно угловые скобки при явном указании типа для шаблонного метода.
Проблема исчезает, если снабдить метод хотя бы одним аргуметном типа T и отказаться от явного указания типа, полагаясь на механизм type inference:
struct Foo {
template <class T>
static int method(T t) { return 0; };
static int method(T t) { return 0; };
};
template<class S, class T>
int f() { T t; return S::method(t); }
int f() { T t; return S::method(t); }
В этом фрагменте уже всё нормально, компилируется. Но нам отнюдь не всегда нужны аргументы в методе. Не передавать же их только для того, чтобы заставить компилятор нормально работать...
Проблема также исчезает, когда мы зовем метод не из самого класса-параметра S, а из какого-то другого класса Traits, зависящего от S:
template<class S>
struct Traits {
template <class T>
static int method() { return 0; };
static int method() { return 0; };
};
template<class S, class T>
int f() { return Traits<S>::method<T>(); }
int f() { return Traits<S>::method<T>(); }
Собственно, это один из способов преодолеть трудность - использовать зависимый класс Traits<S> вместо S для хранения метода, зависящего от T.
Аналогичная проблема проявляется при обращении к вложенному шаблонному классу:
struct Foo {
template<class T>
struct Bar {};
};
template<class T>
struct Bar {};
};
template <class T>
void g() { Foo::Bar<T> bar; }
void g() { Foo::Bar<T> bar; }
Это компилируется. А следующий пример - нет:
template <class S, class T>
void g() { typename S::Bar<T> bar; }
void g() { typename S::Bar<T> bar; }
Даже более простой случай без T не компилируется:
template <class S>
void g() { typename S::Bar<int> bar; }
void g() { typename S::Bar<int> bar; }
Ошибка та же, но сообщение об ошибке уже немного другое:
test.cpp: error: expected primary-expression before '>' token
test.cpp: error: expected primary-expression before ')' token
int *p = new char[ streamBufferSize<MyStream, int>() ];
template <class Stream, class Record>
struct Traits {
static int bufferSize() { return 1024; }
};
...
int *p = new char[ Traits<MyStream, int>::bufferSize() ];
Но этот (универсальный) способ требует дополнительных телодвижений и усложняет код.
... (дописать)
Кстати, именно этот вариант сообщения об ошибке навел меня на "правильное" решение проблемы.
Итак, внимание, правильный ответ. Нужно использовать ключевое слово template в непривычном для нас месте:
template<class S, class T>
Кстати, именно этот вариант сообщения об ошибке навел меня на "правильное" решение проблемы.
Итак, внимание, правильный ответ. Нужно использовать ключевое слово template в непривычном для нас месте:
template<class S, class T>
int f() { return S:: template method<T>(); }
template<class S, class T>
void g() { typename S:: template Bar<T> bar; }
Надо отметить, что в VisualStudio 2008 указанная проблема не наблюдается - там слово template можно писать, а можно нет, всё и так компилируется. (Вопрос: может, там и typename писать не надо?)
"Когда такое нужно?" - спросите Вы. Ну, напрмер, мы имеем дело с различными потоками ввода-вывода, где статический шаблонный метод сообщает нам размер буфера, отводимого в этом потоке при чтении записей заданного типа:
struct MyStream {
template <class Record>
static int bufferSize() { return 1024; }
};
template<class Stream, class Record>
int streamBufferSize() { return Stream::template bufferSize<T>(); }
...
...
int *p = new char[ streamBufferSize<MyStream, int>() ];
Либо можно прибегнуть к вспомогательному классу Traits:
template <class Stream, class Record>
struct Traits {
static int bufferSize() { return 1024; }
};
...
int *p = new char[ Traits<MyStream, int>::bufferSize() ];
Но этот (универсальный) способ требует дополнительных телодвижений и усложняет код.
Какие есть теоретические обоснования рассмотренному тут феномену, мне на данный момент неизвестно.
Уххх, спасибо, значит, это не у меня глюки: довольно больно наступил на грабли с вызовом шаблонной статической функции у класса, являющегося параметром шаблона. Придётся теперь перепахать кусок разрабатываемой библиотеки, чтобы как-то уйти от проблемы. А так казалось всё красиво и заманчиво...
ОтветитьУдалитьСпасибо, помогло!
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьСпасибо, для моего случая подошло отлично! Для методов класса тоже работает. Пример на ideone.
ОтветитьУдалитьСпасибо, как раз то что панадобилось, а то на VS идет а на другие платформы нет.
ОтветитьУдалить