Специализированный шаблонный метод C ++ никогда не вызывался - PullRequest
1 голос
/ 28 марта 2011

И еще одна проблема специализации шаблонов, которую я не могу решить:

terminallog.hh

//stripped code

class Terminallog {
public:

    Terminallog();
    Terminallog(int);
    virtual ~Terminallog();

    template <class T>
    Terminallog & operator<<(const T &v);
    template <class T>
    Terminallog & operator<<(const std::vector<T> &v);
    template <class T>
    Terminallog & operator<<(const T v[]);
    Terminallog & operator<<(const char v[]);

    //stripped code 
};

Terminallog.hh продолжение (отредактировано благодаря комментарию)

//stripped code 

template <class T>
Terminallog &Terminallog::operator<<(const T &v) {
    std::cout << std::endl;
    this->indent();
    std::cout << v;
    return *this;
}

template <class T>
Terminallog &Terminallog::operator<<(const std::vector<T> &v) {
    for (unsigned int i = 0; i < v.size(); i++) {
        std::cout << std::endl;
        this->indent();
        std::cout << "Element " << i << ": " << v.at(i);
    }
    return *this;
}

template <class T>
Terminallog &Terminallog::operator<<(const T v[]) {
    unsigned int elements = sizeof (v) / sizeof (v[0]);
    for (unsigned int i = 0; i < elements; i++) {
        std::cout << std::endl;
        this->indent();
        std::cout << "Element " << i << ": " << v[i];
    }
    return *this;
}

inline
Terminallog &Terminallog::operator<<(const char v[]) {
    std::cout << std::endl;
    this->indent();
    std::cout << v;
    return *this;
}

//stripped code 

Это компилируется просто отлично, без ошибок. Однако, когда я пытаюсь сделать что-то вроде:

Terminallog clog(3);
int test[] = {5,6,7,8};
clog << test;

он всегда печатает мне адрес указателя массива. Другими словами специализированный шаблон

Terminallog & operator<<(const T v[]);

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

Terminallog & operator<<(const T &v);

а не специализация. Очевидно, в моем коде должна быть ошибка, однако я не могу ее найти.

Ответы [ 7 ]

3 голосов
/ 28 марта 2011

Ставлю, что здесь применяются правила конвертации. Так как нет точного соответствия для int [5] (который является фактическим типом вашего массива), ваш массив уменьшится до int* и будет выбрана перегрузка с const T&, так как это будет лучшее соответствие, чем const T v[] (который рассматривается как const T* v).

См. Ответ @sth для подробного объяснения механизма разрешения перегрузки в этом случае.

Что если вы попробуете:

template <class T, size_t n>
Terminallog & operator<<(const T (&v)[n]);

вместо?

Между прочим, sizeof в определении перегрузки с помощью T[] совершенно неверно. Вы не можете получить размер таким образом. Снова массив будет уменьшаться до указателя, и elements всегда будет sizeof(T*) / sizeof(T).

3 голосов
/ 28 марта 2011

В вашем коде вы определяете несколько перегруженных шаблонов функций (они не являются специализациями какого-либо универсального шаблона, они являются отдельными перегрузками. Но в этом нет ничего плохого, нет причины, по которой они должны быть специализациями.)

Один из этих шаблонов имеет объявление параметра const T v[]. Поскольку массивы нельзя передавать по значению, это интерпретируется компилятором так же, как если бы параметр был объявлен const T *v.

Для массива в вопросах, который в конечном итоге будет иметь тип int[5], компилятор должен выбрать один из двух соответствующих шаблонов. Наилучшее совпадение определяется количеством и типом необходимых преобразований в соответствии с §13.3.3.1.1 (таблица 10) стандарта.

  • Шаблон const T& соответствует T = int[5]. Согласно §13.3.3.1.4 / 2 преобразование int[5] в const int(&)[5] параметр требует тех же преобразований, что и преобразование int[5] в const int[5]. Это одно преобразование квалификации (добавление const).
  • Шаблон const T* соответствует T = int. Преобразование int[5] в const int* требует двух преобразований. Сначала и преобразование массива в указатель (int[5] в int*), затем преобразование квалификации (int* в const int*).

Все эти преобразования квалифицируются как "точные совпадения", но поскольку для второго шаблона потребуются два таких преобразования, в то время как для первого шаблона требуется только одно, первый шаблон будет лучше соответствовать.

Чтобы получить «правильное» соответствие, вы можете удалить const из параметра второго шаблона или добавить дополнительный шаблон для неконстантных указателей, который просто вызывает версию const:

template <class T>
Terminallog& Terminallog::operator<<(T *v) {
   *this << static_cast<const T*>(v);
}

При этом обратите внимание, что вы не можете получить длину массива с помощью sizeof в такой функции, как эта. Шаблон с дополнительным параметром размера, подобный предложенному Александром С. в его ответе, может быть лучшим выбором для этого.

2 голосов
/ 28 марта 2011

Прежде всего, у вас нет специализаций здесь, но есть перегруженные функции.

Затем я предполагаю, что проблема заключается в следующем:

int test[] = {5,6,7,8}; // <-- this guy is "decayed" to int* in next call
clog << test;

Итак, теперь во время перегрузки компилятор разрешениявыбирает между

template <class T>
Terminallog & operator<<(const T &v);

template <class T>
Terminallog & operator<<(const T v[]);

Первый - точное совпадение, поэтому он "выигрывает".

1 голос
/ 28 марта 2011

Во-первых: не существует такой вещи, как внешние шаблоны (в стандарте C ++ было ключевое слово экспорта, но оно было проигнорировано основными производителями компиляторов, такими как MS и GNU, и теперь кажется заброшенным).Поэтому вы должны поместить тела шаблонных функций в заголовочный файл.

Второе: лучше забудьте частичную специализацию шаблона.Он не поддерживается достаточно хорошо, например, MS обеспечивает очень ограниченную поддержку частичной специализации шаблонов классов (для указателей, ссылок, указателей на члены и указатели на функции (см. Здесь) ).Так что лучше просто не используйте его.Но вы можете использовать полностью явную специализацию шаблонов.

В-третьих: в вашем коде на самом деле нет шаблонизаций специализаций

template <class T>
Terminallog & operator<<(const T &v);
template <class T>
Terminallog & operator<<(const std::vector<T> &v);
template <class T>
Terminallog & operator<<(const T v[]);

- это три различных функциональных шаблона и

Terminallog & operator<<(const char v[]);

- это просто функция.

Правильный синтаксис для специализаций шаблонов функций таков:

template <class T>
Terminallog& out(const T& v)
{
// default implementation
}

template <class T>
Terminallog& out< std::vector<T> >(const std::vector<T>& v)
{
// partially specialized implementation
}

template <>
Terminallog& out<double>(const double& v)
{
// fully specialized implementation
}

Но это не совсем так.Разрешение перегрузки по-прежнему должно приводить к наиболее специализированной функции или шаблону функции (если такой функции не существует) в соответствии с правилами, определенными в стандарте.Но я не уверен, что существует полностью совместимая реализация C ++ (кроме Comeau C ++, разработанного стандартными авторами, никем не используемыми).Я думаю, что если у вас есть две перегрузки, которые точно совпадают или не соответствуют ни одной из них (и требуется неявное преобразование), у вас могут возникнуть проблемы с несоответствием.

ТАКЖЕ ПРИМЕЧАНИЕ:

Специализации шаблонов функций разрешены только в области имен.Это означает, что вы не можете объявлять специализации шаблона функции-члена.Но, конечно, вы можете определить перегрузки, как вы это сделали.

1 голос
/ 28 марта 2011

Прежде всего вы не занимаетесь специализацией шаблонов:

template <class T>
Terminallog & operator<<(const T v[]);
Terminallog & operator<<(const char v[]);

- это две разные функции. Если вы попытались определить вывод, где T имеет тип char, то ваш компилятор должен пожаловаться на неоднозначность . Чтобы указать, что вы специализируете шаблон, как отмечает ssteinberg, вам нужно использовать нотацию template<>.

В этом случае, однако, это, вероятно, не поможет вам, так как я не верю, что вы можете специализировать функции-члены (может быть, вы можете, если они статические?). Поэтому ваш компилятор будет жаловаться, если вы попытаетесь последовать совету Штейнберга. Вам нужно шаблонировать весь класс, а затем специализировать отдельные функции.

Следующая ссылка может оказать некоторую помощь, http://womble.decadent.org.uk/c++/template-faq.html#mem-fun-specialisation

EDIT:

Следующее может быть иллюстративным:

#include <vector>
#include <iostream>

template<class T>
class Terminallog { 
public:

  Terminallog(){};
  Terminallog(int){};
  virtual ~Terminallog(){};

  //general vector output: will be specialized for vectors of chars
  Terminallog & 
  operator<<(const std::vector<T> &v);

  //general reference output: will be specialized for chars
  Terminallog & operator<<(const T &v);

  //general pointer output: will be specialised for char pointers
  Terminallog & operator<<(const T* v);

  //stripped code 
};

//general code for reference type
 template <class T>
 Terminallog<T>&
 Terminallog<T>::operator<<(const T &v) {
   std::cout<<"This general reference"<<std::endl;
     return *this;
 }

//specialisation for chars reference
template <>  //as noted by ssteinberg
Terminallog<char>&
Terminallog<char>::operator<<(const char &v) {
  std::cout<<"This is for chars"<<std::endl;
  return *this;
}

//general code for pointer type
 template <class T>
 Terminallog<T>&
 Terminallog<T>::operator<<(const T* v) {
   std::cout<<"This general pointers"<<std::endl;
     return *this;
 }

//specialisation for chars pointer
//as noted by alexandre your array will decay to this....
template <>  
Terminallog<char>&
Terminallog<char>::operator<<(const char* v) {
  std::cout<<"This is for chars pointers"<<std::endl;
  return *this;
}


//Non specialised vector
template <class T>
Terminallog<T>&
Terminallog<T>::operator<<(const std::vector<T> &v) {
  std::cout<<"This general vector"<<std::endl;
  return *this;
}

//specialisation for  vector of chars
template <>
Terminallog<char>&
Terminallog<char>::operator<<(const std::vector<char> &v) {
  std::cout<<"This is a vector of chars"<<std::endl;
  return *this;
}

int
main  (int ac, char **av)
{
  Terminallog<int> ilog(3);
  int testint[] = {5,6,7,8};
  std::vector<int> testvi;
  testvi.push_back(1);
  testvi.push_back(3);
  testvi.push_back(5);

  Terminallog<char> clog(3);
  char testchar[] = {5,6,7,8};
  std::vector<char> testvc;
  testvc.push_back(1);
  testvc.push_back(3);
  testvc.push_back(5);

  ilog << testint;
  ilog << testvi;
  clog << testchar;
  clog << testvc;


}

Вывод

This general pointers
This general vector
This is for chars pointers
This is a vector of chars
0 голосов
/ 28 марта 2011

Явный специализированный экземпляр шаблона:

inline template<>
Terminallog & operator<<(const char v[]);

Примерно так.Мой C ++ ржавый.

0 голосов
/ 28 марта 2011

Попробуйте поставить template<> перед Terminallog & operator<<(const char v[]);, чтобы сообщить компилятору, что вы специализируете шаблон.

...