Есть в шаблонах С++ интересная возможность параметризировать сами параметры шаблонов. Чтобы это могло значить?
Бывают случаи, когда параметр шаблона сам является шаблонным классом и для его инстанцирования тоже нужно указать параметр. Например, универсальная шаблонная функция для печати стандартного контейнера любого типа в поток:
template< typename C, typename E > void print(const C<E>& v) { copy(v.begin(), v.end(), ostream_iterator<E>(cout, " ")); cout << endl; }
И все бы ничего, но с только зрения синтаксиса С++ это неверно. Нельзя просто написать C<E>
, если E
сам является не определенным типом, а параметром шаблона. Правильный способ использования параметра шаблона, который в свою очередь зависит от другого параметра, должен выглядеть так:
template< template<typename> class C, typename E > void print(const C<E>& v) { copy(v.begin(), v.end(), ostream_iterator<E>(cout, " ")); cout << endl; }
Теперь полный пример (template_parameter.cpp
):
#include <iostream> #include <iomanip> #include <algorithm> #include <iterator> #include <string> #include <vector> #include <list> #include <deque> // Я обычно не использую пространства имен "по умолчанию", но тут // это сделано для компактности примера. using namespace std; // Вся изюминка тут: template<typename> или template<class>. // Без этого параметр шаблона "С" нельзя будет параметризировать. // в конструкции C<E>&. template< template<typename> class C, typename E > // Тут происходит параметризация параметра "С" параметром "E". // Без этого класс "С" не может быть использован, так как "E" // является не просто типом, а тоже параметром шаблона. void print(const C<E>& v) { // Так как класс элемента контейнера "Е" нам тут нужен как отдельный // тип, то для этого и затеяна вся тема с параметризированными // параметрами шаблона. copy(v.begin(), v.end(), ostream_iterator<E>(cout, " ")); cout << endl; } // Тестовая программа демонстрирует, как одна функция print() // может использоваться для печати любого контейнера // (если, конечно, он удовлетворяет требованиям алгоритма // copy() по наличию должных итераторов), содержащего элементы // любого типа. int main(int argc, char* argv[]) { // Массив целых. int i[5] = { 1, 2, 3, 4, 5 }; // Создаем вектор, состоящий из целых, и печатаем его. print< vector, int >( vector<int>(i, i + 5) ); // Массив вещественных. float f[5] = { .1, .2, .3, .4, .5 }; // Создаем вектор, состоящий из вещественных, и печатаем его. print< vector, float >( vector<float>(f, f + 5) ); // Массив символов. char c[5] = { 'a', 'b', 'c', 'd', 'e' }; // Создаем деку, состоящую их символов, и печатаем ее. print< deque, char >( deque<char>(c, c + 5) ); // Массив строк в стиле С. char* s[5] = { "a1", "b2", "c3", "d4", "e5" }; // Создаем список, состоящий из строк, и печатаем его. print< list, string >( list<string>(s, s + 5) ); return 0; }
Компилируем.
Cygwin:
g++ -o template_parameter_cygwin.exe template_parameter.cpp
или в Borland/Codegear Studio 2007:
bcc32 /etemplate_parameter_cg2007.exe template_parameter.cpp
И запускаем скомпилированный файл:
1 2 3 4 5
0.1 0.2 0.3 0.4 0.5
a b c d e
a1 b2 c3 d4 e5
Отчетливо видно, что на первой строке распечатаны целые, на второй вещественные, на третьей символы, и на четвертой строки.
Вы спросите, где компиляция в Visual Studio? А вот с ней вышел облом. Я пробовал скомпилировать этот пример в Visual Studio 2005 и 2008, и в обоих случаях я получал ошибки типа:
template_as_parameter.cpp(38) : error C3208: 'print' : template parameter list for class template 'std::vector' does not match template parameter list for template template parameter 'C'
Из чего я сделал вывод, что микрософтовский компилятор не поддерживает подобный синтаксис.
Я был очень расстроен подобным фактом, так как в целом очень положительно отношусь к cl.exe
. А тут выходит, что даже борландовый компилятор это понимает, а cl.exe
нет. Если кто знает, может есть ключик какой секретный для включения поддержки “хитрых и редких” возможностей С++ в компиляторе микрософта — научите, пожалуйста. Буду очень признателен.
Предвосхищу вопросы типа “зачем так сложно, да еще и плохо переносимо” — все верно. Лично я бы отнес все выше описанное к “темным углам” С++, но уж больно интересно по ним полазать.
Обновление
Комментарий Александра прояснил ситуацию с проблемой при компиляции в Visual Studio. Окончательный вариант кода, чтобы работало в cl.exe
, таков:
template< template<typename, typename> class C, typename E > void print(const C<E, allocator<E> >& v) { copy(v.begin(), v.end(), ostream_iterator<E>(cout, " ")); cout << endl; }
У шаблонов стандартных контейнеров есть второй параметр, так называемый allocator
. Этот параметр часто используется со значением по умолчанию, поэтому редко приходится вспоминать о нем. И как уточнил Александр, моя проблема была в том, что cl.exe
требует явного указания наличия этого параметра при параметризации параметра C
.
Исправленный код компилируется во всех опробованных компиляторах, теперь включая и cl.exe
.
Другие посты по теме: