简体   繁体   中英

C++ Class Template Specialization

I am new to programming in C++ and I have encountered a problem that I cannot seem to solve when enforcing separate compilation. I am trying to specialize my class tokenize to add a dtor for a specific type ( istream ). I have the following:

#ifndef __TOKENIZER_H__
#define __TOKENIZER_H__

#include <fstream>
#include <string>

template <class T>
class base {
  // ... some methods/member variables.
};

template <class T>
class tokenizer : public base<T> {
  public:
    tokenizer(T &in);
};

template <>
class tokenizer<std::ifstream> : public base<std::ifstream> {
  public:
    tokenizer(std::ifstream &in);
    ~tokenizer();
};

#endif

... and:

#include "tokenizer.h"

#include <fstream>
#include <iostream>
#include <locale>

using std::ifstream;
using std::istream;
using std::string;

// [BASE]

// ... code for those functions.

// [TOKENIZER]

// See header file.
template <class T>
tokenizer<T>::tokenizer(T &in) : base<T>(in) { }

// See header file.
template <>
tokenizer<ifstream>::tokenizer(ifstream &in) : base<ifstream>(in) { }

// See header file.
template <>
tokenizer<ifstream>::~tokenizer() {
  delete &(base<ifstream>::in);
}

// Intantiating template classes (separate compilation).
template class base<std::ifstream>;
template class base<std::istream>;
template class tokenizer<std::ifstream>;
template class tokenizer<std::istream>;

... however I get the following error:

tokenizer.cc:62: error: template-id ‘tokenizer<>’ for ‘tokenizer<std::basic_ifstream<char, std::char_traits<char> > >::tokenizer(std::ifstream&)’ does not match any template declaration
tokenizer.cc:66: error: template-id ‘tokenizer<>’ for ‘tokenizer<std::basic_ifstream<char, std::char_traits<char> > >::~tokenizer()’ does not match any template declaration

I am compiling with g++. If someone can kindly point out what I am missing and a possible explanation then that would be fantastic. I am confused how templates work with separate compilation (defns/decl separated).

[temp.expl.spec]/5 states:

Members of an explicitly specialized class template are defined in the same manner as members of normal classes, and not using the template<> syntax. The same is true when defining a member of an explicitly specialized member class. However, template<> is used in defining a member of an explicitly specialized member class template that is specialized as a class template.

It also provides the following example (I'll only quote some excerpts):

 template<class T> struct A { template<class U> struct C { }; }; template<> struct A<int> { void f(int); }; // template<> not used for a member of an // explicitly specialized class template void A<int>::f(int) { /∗ ... ∗/ } template<> template<class U> struct A<char>::C { void f(); }; // template<> is used when defining a member of an explicitly // specialized member class template specialized as a class template template<> template<class U> void A<char>::C<U>::f() { /∗ ... ∗/ } 

As far as I know, once you've explicit specialized a class template, you've created a "normal class". It's obviously not a template any more (you cannot create classes from the specialization), but a type with some <..> in its name.

In your case, that just means leave out the template<> before

 // See header file. //template <> tokenizer<ifstream>::tokenizer(ifstream &in) : base<ifstream>(in) { } // See header file. //template <> tokenizer<ifstream>::~tokenizer() { delete &(base<ifstream>::in); } 

With regard to your request on clarification of the combination of separate compilation with templates:

When you use a class template to create an object (eg std::vector<int> v ) or call a function template (eg std::sort(begin(v), end(v)) ), you're dealing with specializations of the templates. std::vector<int> is a specialization of the class template std::vector .

When a specialization is required in a TU, it might be necessary to produce it from the class template. This is called instantiation . An explicitly specialized template won't be instantiated implicitly (it already is specialized). That is, your specialization tokenizer<ifstream> doesn't have to be instantiated in any TU.

Templates itself don't work with separate compilation for these reasons. However, you can use explicit instantiations and explicit specializations to provide the benefits of separate compilation for specializations of templates. For example:

[header.hpp]

template<class T> void foo(T);
extern template void foo<int>(int);

[impl.cpp]

#include "header.hpp"

template<class T> void foo(T) { return T{} };

template void foo<int>(int); // force instantiation

[main.cpp]

#include "header.hpp"

int main()
{
    foo<int>(42); // no instantiation will occur
}

In main.cpp we couldn't instantiate the definition of foo , as the definition is not available. We could instantiate the declaration. There's also an explicit instantiation declaration, which prevents any implicit instantiation. In another TU (impl.cpp), we did instantiate foo<int> via an explicit instantiation definition . This requires the definition of f to exist and instantiates the definition. The rest is similar to normal functions: We have two declarations and one definition.

Similarly, for class templates: If the definition of a class is required in a TU, we need to either instantiate the template or we need to have an explicit specialization (an explicit instantiation definition is not possible here AFAIK). This is exactly the OP's example.

If the definition of the class is not required, we can use something similar to the PIMPL idiom:

[header.hpp]

template<class T>
class foobar;

struct s
{
    foobar<int>* p;
    void f();
}

[impl.cpp]

#include "header.hpp"

template<class T> class foobar { int i; }

void s::f() { p = new foobar{42}; }

[main.cpp]

int main()
{
    s obj;
    obj.f();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM