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

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

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

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.

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


Оригинальный пост | Disclaimer

Комментарии