*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

пятница, 13 февраля 2009 г.

Шаблоны как параметры шаблона

Есть в шаблонах С++ интересная возможность параметризировать сами параметры шаблонов. Чтобы это могло значить?

Бывают случаи, когда параметр шаблона сам является шаблонным классом и для его инстанцирования тоже нужно указать параметр. Например, универсальная шаблонная функция для печати стандартного контейнера любого типа в поток:

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.


Другие посты по теме: