Рекурсивный AsString () для печати контейнеров STL в C ++ - PullRequest
3 голосов
/ 27 января 2011

Я пытаюсь написать функцию AsString (), которая преобразует контейнеры STL в строку по своему вкусу.Вот код, который я придумала до сих пор:

template<class T>
inline string AsString(const T& v);

template<class First, class Second>
inline string AsString(const pair<First, Second>& p);

template<class Iter>
inline string PrintSequence(const char* delimiters, Iter begin, Iter end) {
  string result;
  result += delimiters[0];
  int size = 0;
  for (size = 0; begin != end; ++size, ++begin) {
    if (size > 0) {
      result += ", ";
    }
    result += AsString(*begin);
  }
  result += delimiters[1];
  result += StringPrintf("<%d>", size);
  return result;
}

#define OUTPUT_TWO_ARG_CONTAINER(Sequence) \
template<class T1, class T2> \
inline string AsString(const Sequence<T1, T2>& seq) { \
  return PrintSequence("[]", seq.begin(), seq.end()); \
}

OUTPUT_TWO_ARG_CONTAINER(vector)
OUTPUT_TWO_ARG_CONTAINER(deque)
OUTPUT_TWO_ARG_CONTAINER(list)

template<class First, class Second>
inline string AsString(const pair<First, Second>& p) {
  return "(" + AsString(p.first) + ", " + AsString(p.second) + ")";
}

template<class T>
inline string AsString(const T& v) {
  ostringstream s;
  s << v;
  return s.str();
}

Как видите, основная идея заключается в том, что AsString() рекурсивно вызывает себя в контейнерах STL, а затем переходит к обычному operator<<() (причина, по которой я не хочу переопределять operator<<(), заключается в том, что я не хочу вмешиваться в другие библиотеки, которые делают именно это).

Теперь AsString() компилируется и работает на мелких контейнерах,но не для вложенных:

vector<int> v;
v.push_back(1);
v.push_back(2);
AsString(v) == "[1, 2]<2>";  // true

vector<vector<int> > m;
m.push_back(v);
m.push_back(v);
AsString(m) == "[[1, 2]<2>, [1, 2]<2>]<2>";  // Compilation Error!!!

Компилятор по какой-то причине хочет использовать operator<<() при попытке печати элементов `m ', несмотря на то, что я предоставил специализацию шаблона длявекторы.

Как я могу заставить AsString() работать?

ОБНОВЛЕНИЕ : ОК, оказывается, порядок определений имеет значение (по крайней мере, для этого компилятора - gcc4.4.3).Когда я сначала добавлю определения макросов, компилятор правильно их подберет и отобразит вектор векторов.Необъяснимые.

Ответы [ 2 ]

3 голосов
/ 27 января 2011

Мир шаблонов замечательный ... и настоящая ловушка для неосторожных ...

Специализация берет существующую функцию шаблона и указывает все ее аргументы.

Перегрузка использует то же имя, что и другая функция (шаблонная или нет) для другого набора аргументов.

template <typename T>
void foo(T const& t);

template <>
void foo<int>(int i); // this is a "complete" specialization

template <typename T, typename U>
void foo<std::pair<T,U>>(std::pair<T,U> const& pair);
  // this is a "partial" specialization
  // and by the way... it does NOT COMPILE

template <typename T, typename U>
void foo(std::pair<T,U> const& pair); // this is an overload

Обратите внимание на синтаксическую разницу, в перегрузке нет <xxxx> после идентификатора (foo здесь).

В C ++ невозможно частично специализировать функцию; то есть оставить некоторую универсальность в аргументах. Вы можете либо перегрузить, либо полностью специализироваться: обязательное чтение на данный момент ПОЛУЧИЛ # 49: Специализация шаблона и перегрузка

Следовательно, выбор между:

template <typename T>
std::string AsString(const T& v); // (1)

template <typename T, typename Allocator>
std::string AsString(std::vector<T, Allocator> const& v); // (2)

И настоящий вопрос: какой тип *begin?

Ну, m не является константным:

  • Iter логически есть std::vector< std::vector<int> >::iterator.
  • тип *begin, таким образом, std::vector<int>&

Итак, две перегрузки учитываются с помощью:

  • (1): T = std::vector<int>, требуется преобразование в const-ref
  • (2): T = int, U = std::allocator<int>, требуется преобразование в const-ref

Второй должен быть выбран, потому что он, насколько я понимаю, ближе к реальному типу. Я протестировал его с VC ++ 2010, и он действительно был выбран.

Не могли бы вы также объявить неконстантную версию векторной перегрузки и посмотреть, умиротворяет ли она ваш компилятор? (кстати, я хотел бы узнать имя;)).

1 голос
/ 27 января 2011

Вы не указали специализацию, у вас перегружено AsString. Как оказалось, ваша более поздняя перегрузка не предпочтительнее, чем T const & version.

Вместо этого overload op << в специальном пространстве имен </a> для различных контейнеров stdlib. Пространство имен важно, чтобы вы не влияли на другой код, но вы явно будете использовать его в AsString:

namespace make_sure_to_put_these_overloads_in_a_namespace {

// Your PrintSequence adapted to a stream instead of a string:
template<class Iter>
void PrintSequence(std::ostream &s, const char* delim,
                   Iter begin, Iter end)
{
  s << delim[0];
  int size = 0;
  if (begin != end) {
    s << *begin;
    ++size;
    while (++begin != end) {
      s << ", " << *begin;
      ++size;
    }
  }
  s << delim[1] << '<' << size << '>';
}

#define OUTPUT_TWO_ARG_CONTAINER(Sequence) \
template<class T1, class T2> \
std::ostream& operator<<(std::ostream &s, Sequence<T1, T2> const &seq) { \
  PrintSequence(s, "[]", seq.begin(), seq.end()); \
  return s; \
}

OUTPUT_TWO_ARG_CONTAINER(std::vector)
OUTPUT_TWO_ARG_CONTAINER(std::deque)
OUTPUT_TWO_ARG_CONTAINER(std::list)
// other types
#undef OUTPUT_TWO_ARG_CONTAINER

template<class First, class Second>
std::ostream& operator<<(std::ostream &s, std::pair<First, Second> const &p) { \
  s << "(" << p.first << ", " << p.second << ")";
  return s;
}

}

template<class T>
std::string AsString(T const &v) {
  using namespace make_sure_to_put_these_overloads_in_a_namespace;
  std::ostringstream ss;
  ss << v;
  return ss.str();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...