Как специализировать шаблон без указания имени класса? - PullRequest
0 голосов
/ 29 января 2012

Я хочу создать функцию с именем debug, которая выводит некоторую информацию об объектах.Моя система содержит объекты разных типов;некоторые из них содержат другие объекты.

using namespace std; // for brevity
struct dog {string name;};
struct human {string name; string address;};
struct line {list<human*> contents;};
struct pack {vector<dog*> contents;};

Я хочу, чтобы функция выводила член name аргумента, если он есть, или отлаживала член contents аргумента, если он есть.Я придумал следующий код:

template <class T>
void debug(T object) // T here is a simple object like dog, human, etc
{
    cout << object.name.c_str() << '\n';
}

// A helper function, not really important
template <class T>
void debug_pointer(T* object)
{
    debug(*object);
}

void debug(pack object)
{
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<dog>);
}

void debug(line object)
{
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<human>);
}

Здесь код для pack и line практически идентичен!Я хотел бы избежать написания одного и того же кода несколько раз:

struct line {list<human*> contents; typedef human type;};
struct pack {vector<dog*> contents; typedef dog type;};

template <class T>
void debug(T object) // T here is a compound object (having contents)
{
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<T::type>);
}

Но этот синтаксис конфликтует с шаблоном функции для "простых" объектов (имеет одинаковую подпись).

Как можноя переписал свой код?Я не хочу переписывать первую часть (объявления для dog, human и т. Д.), Потому что эта часть моей программы уже очень сложна, и для нее добавляются вещи (базовые классы, функции-члены и т. Д.) Только дляотладка кажется неуместной.

Ответы [ 4 ]

1 голос
/ 29 января 2012

Использование C ++ 11, decltype и SFINAE упрощает работу:)

#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>

struct dog { std::string name; };
struct human { std::string name; std::string address; };
struct line { std::list<human*> contents; };
struct pack { std::vector<dog*> contents; };

template <typename T>
auto debug(T const& t) -> decltype(t.name, void(0)) {
  std::cout << t.name << '\n';
}

template <typename T>
auto debug(T const* t) -> decltype(t->name, void(0)) {
  if (t != 0) std::cout << t->name << '\n';
}

struct Debugger {
  template <typename T>
  void operator()(T const& t) { debug(t); }
};

template <typename C>
auto debug(C const& c) -> decltype(c.contents, void(0)) {
  typedef decltype(c.contents) contents_type;
  typedef typename contents_type::value_type type;
  std::for_each(c.contents.begin(), c.contents.end(), Debugger());
}


int main() {
  dog dog1 = { "dog1" }, dog2 = { "dog2" };
  human h1 = { "h1" }, h2 = { "h2" };

  line l; l.contents.push_back(&h1); l.contents.push_back(&h2);

  debug(l);

}

В действии на ideone это дает:

h1
h2

как и ожидалось:)

Без C ++ 11 требуется немного хитрости, но принцип остается тем же, используя boost::enable_if, вам необходимо создать структуру, которая будет вызывать ошибку компиляции на основе наличия и доступности name и contents.

Конечно, было бы проще, если бы вы просто подключили методы в самих структурах:)

1 голос
/ 29 января 2012

Базовый код может выглядеть следующим образом:

template <typename T> void debug(T const & x)
{
    debug_helper<T, has_name<T>::value>::print(x);
}

Нам нужен вспомогательный класс:

template <typename, bool> struct debug_helper;

template <typename T> struct debug_helper<T, true>
{
    static void print(T const & x) { /* print x.name */ }
};
template <typename T> struct debug_helper<T, false>
{
    static void print(T const & x) { /* print x.content */ }
};

Теперь нам просто нужен класс характеристик SFINAE has_name<T> и механизмпечатать контейнеры.Обе эти проблемы решаются почти дословно в симпатичном коде принтера .

1 голос
/ 29 января 2012

Сделайте контейнер также параметром шаблона:

template <template <typename> class Container, typename T>
void debug(Container<T> object)
{
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<T>);
}

Кстати, в большинстве случаев вы можете захотеть передать по константной ссылке, а не по значению (что требует копирования всего вектора / списка):

template <template <typename> class Container, typename T>
void debug(const Container<T>& object)

Если можно использовать C ++ 11, вы можете использовать decltype для определения T из содержимого:

template <typename T>
void debug(const T& object)
{
    typedef decltype(*object.contents.front()) T;
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<T>);
}

GCC также имеет typeof, когда C ++ 11 не может быть использован.

0 голосов
/ 29 января 2012

Вы можете использовать SFINAE для выбора используемой перегрузки.

Я забыл точные детали, но вы можете использовать его для обнаружения присутствия элемента "содержимого" или элемента "имя", а затем перегрузки на основе этого.

...