ftiaronsem
ftiaronsem

Reputation: 1584

Specialized C++ template method never called

And another template specialization problem, which I can't resolve:

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 continued (edited thanks to comment)

//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 

This compiles just fine, no errors. However when I try to do something like:

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

it always prints me the pointer-address the of the array. In other words the specialized template

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

is never called. I also verified this with an additional cout. No matter what I try the program is always calling

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

and not the specialization. Obviously there has to be an error in my code, however I can't find it.

Upvotes: 1

Views: 1577

Answers (7)

Tom
Tom

Reputation: 5299

First of all you are not doing template specialization:

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

are two different functions. If you tried to define an output where T is of type char then your compiler should complain of an ambiguity. To indicate that you are specializing a template, as ssteinberg notes, you need to use the template<> notation.

In this case, however, this probably won't help you as I don't believe you can specialize member functions (maybe you can if they are static?). So your compiler will complain if you try to follow ssteinberg's advice. You need to template the whole class, and then specialize individual functions.

The following link might provide some help, http://womble.decadent.org.uk/c++/template-faq.html#mem-fun-specialisation

EDIT:

The following might be illustrative:

#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;


}

Output is

This general pointers
This general vector
This is for chars pointers
This is a vector of chars

Upvotes: 1

Serge Dundich
Serge Dundich

Reputation: 4429

First: there is no such thing as extern templates (there was export keyword in C++ standard but it was ignored by major compiler producers like MS and GNU and now seems abandoned). So you have to put template function bodies in header file.

Second: better forget partial template specialization. It is not supported well enough, e.g. MS provides only very limited support for partial class template specialization (for pointers, references, pointer to member and function pointers (Look here)). So better just don't use it. But you can use fully explicit template specialization.

Third: you don't really have any template specializations in your code

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

are three distinct function templates and

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

is just function.

The right syntax for function template specializations is this

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
}

But it is not really the point. The overloading resolution must still lead to the most specialized function or function template (if no such function exist) according to the rules defined in the standard. But I'm not sure that fully compliant C++ implementation exist (except Comeau C++ developed by standard authors used by nobody). I think that if you have two overloads that match exactly or none of them (and implicit conversion is required) you may have problems with non-compliance.

ALSO NOTE:

Function template specializations are allowed in namespace scope only. It means that you may not declare member function template specializations. But of course you may define overloads like you did.

Upvotes: 1

sth
sth

Reputation: 229593

In your code, you define several overloaded function templates (They are not specializations of some generic template, they are separate overloads. But there is nothing wrong with that, there is no reason they would have to be specializations.)

One of these templates has a parameter declaration of const T v[]. Since arrays cannot be passed by value, this is interpreted by the compiler just the same as if the parameter was declared const T *v.

For the array in questions, which ends up to be of type int[5], the compiler has to chose between two matching templates. The best match is determined by the number and type of conversions needed, according to §13.3.3.1.1 (Table 10) in the standard.

  • The const T& template matches for T = int[5]. According to §13.3.3.1.4/2 converting the int[5] to a const int(&)[5] parameter requires the same conversions as converting an int[5] to a const int[5]. This is one qualification conversion (adding a const).
  • The const T* template matches for T = int. Converting a int[5] to a const int* requires two conversions. First and array-to-pointer conversion (int[5] to int*), then a qualification conversion (int* to const int*).

All these conversions qualify as "exact matches", but since the second template would require two such conversions while the first template requires only one, the first template is a better match.

To get the "correct" match, you could remove the const from the parameter of the second template, or add an additional template for non-const pointers that just calls the const version:

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

All that being said, note that you cannot get the array length with sizeof in a function like this. A template with an additional size parameter like suggested by Alexandre C. in his answer might be a better choice for that.

Upvotes: 3

Alexandre C.
Alexandre C.

Reputation: 56956

My bet is that conversion rules are applied here. Since there is no exact match for int [5] (which is the actual type of your array), your array will decay to int* and the overload with const T& will be chosen, since it will be a better match than const T v[] (which is treated as const T* v).

See @sth 's answer for a detailed explanation on the overload resolution mechanism in this case.

What if you try:

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

instead ?

By the way the sizeof stuff in the definition of the overload with T[] is plain wrong. You cannot get the size this way. Again the array will decay to a pointer and elements will always be sizeof(T*) / sizeof(T).

Upvotes: 3

Alexander Poluektov
Alexander Poluektov

Reputation: 8053

First of all, you don't have specializations here, but overloaded functions.

Then, I assume the problem is as follows:

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

So now during overloading resolution compiler chooses between

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

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

First one is exact match, so it "wins".

Upvotes: 2

unexpectedvalue
unexpectedvalue

Reputation: 6139

Explicit specialized template instantiation:

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

Something like this. My C++ is rusty.

Upvotes: 0

Kiril Kirov
Kiril Kirov

Reputation: 38163

Try to put template<> in front of Terminallog & operator<<(const char v[]); to tell the compiler that you're specializing a template.

Upvotes: 0

Related Questions