从未调用过专门的C ++模板方法

时间:2011-03-28 11:28:59

标签: c++ template-specialization

另一个模板特化问题,我无法解决:

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[]);
永远不会打电话给

。我还通过额外的cout验证了这一点。无论我尝试什么,程序总是在调用

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

而不是专业化。显然我的代码中必须有一个错误,但我找不到它。

7 个答案:

答案 0 :(得分:3)

我敢打赌,这里会应用转换规则。由于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)

答案 1 :(得分:3)

在你的代码中,你定义了几个重载的函数模板(它们不是某些通用模板的特化,它们是单独的重载。但是没有任何问题,没有理由它们必须是特化。)

其中一个模板的参数声明为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获取数组长度。具有额外大小参数的模板,如Alexandre C.在他的回答中建议的那样,可能是更好的选择。

答案 2 :(得分:2)

首先,你没有专业化,但是功能过载。

然后,我认为问题如下:

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[]);

第一个是完全匹配,所以它&#34;胜出#34;。

答案 3 :(得分:1)

首先,您没有进行模板专业化:

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

是两个不同的功能。 如果您尝试定义T类型为char的输出,那么您的编译器应该抱怨歧义。为了表明您正在专门化模板,正如ssteinberg所说,您需要使用template<>表示法。

但是,在这种情况下,这可能对您没有帮助,因为我不相信您可以专门化成员函数(如果它们是静态的,也许你可以吗?)。因此,如果您尝试遵循ssteinberg的建议,您的编译器会抱怨。您需要模拟整个类,然后专门化各个函数。

以下链接可能会提供一些帮助,http://womble.decadent.org.uk/c++/template-faq.html#mem-fun-specialisation

修改

以下可能是说明性的:

#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

答案 4 :(得分:1)

首先:没有外部模板这样的东西(在C ++标准中有导出关键字,但它被MS和GNU等主要编译器生产者忽略,现在似乎已经放弃了)。所以你必须将模板函数体放在头文件中。

第二:最好忘记部分模板专业化。它的支持不够好,例如MS仅对部分类模板特化提供非常有限的支持(对于指针,引用,指向成员和函数指针(Look here)的指针)。所以最好不要使用它。但您可以使用完全显式的模板专业化。

第三:您的代码中没有任何模板专业化

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 ++)。我认为如果你有两个完全匹配的重载或者没有一个重载(并且需要隐式转换),你可能会遇到不合规的问题。

还注意:

仅在命名空间作用域中允许使用函数模板特化。这意味着您可能不会声明成员函数模板特化。但是你当然可以像你一样定义重载。

答案 5 :(得分:0)

尝试将template<>放在Terminallog & operator<<(const char v[]);前面,告诉编译器您正在专门化模板。

答案 6 :(得分:0)

明确的专门模板实例化:

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

像这样的东西。我的C ++生锈了。