Search code examples
c++templatestypeid

How can I perform different actions for different template parameters in a template class?


If I want to make a template class, and depending on the typeid of the template parameter perform different actions, then how do I code this?

For instance, I have the following template class, in which I want to initialize the member field data depending on whether it is an int or a string.

#include <string>

template <class T>
class A
{
private:
    T data;
public:
    A();
};

// Implementation of constructor
template <class T>
A<T>::A()
{
    if (typeid(T) == typeid(int))
    {
        data = 1;
    }
    else if (typeid(T) == typeid(std::string))
    {
        data = "one";
    }
    else
    {
        throw runtime_error("Choose type int or string");
    }
}

This code would not compile however, with the following main file.

#include "stdafx.h"
#include "A.h"
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
    A<int> one;
    return 0;
}

The error is: error C2440: '=' : cannot convert from 'const char [2]' to 'int', which means the code is actually checking the else-if statement for an int, even though it will never be able to reach that part of the code.

Next, following this example (Perform different methods based on template variable type), I tried the following A.h file, but I got several linker errors mentioning that A(void) is already defined in A.obj.

#include <string>

template <class T>
class A
{
private:
    T data;
public:
    A();
    ~A();
};

// Implementation of constructor
template <>
A<int>::A()
{
    data = 1;
}
template <>
A<std::string>::A()
{
    data = "one";
}

Does anybody know how to get this code up and running? I also realize that using such an if-else statement in a template class might remove the power from a template. Is there a better way to code this?

EDIT: after discussion with Torsten (below), I now have the following A.h file:

#pragma once

#include <string>

// Class definition
template <class T>
class A
{
public:
    A();
    ~A();
private:
    T data;
};

// Implementation of initialization
template < class T > 
struct initial_data
{
  static T data() { throw runtime_error("Choose type int or string"); }
};

template <> 
struct initial_data< int >
{
    static int data() { return 1; }
};

template <> 
struct initial_data< std::string >
{
    static std::string data() { return "one"; }
};

// Definition of constructor
template <class T>
A<T>::A()
  : data( initial_data< T >::data() ) 
{
}

and the following main:

#include "stdafx.h"
#include "A.h"
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
    A<int> ione;

    return 0;
}

The linker error I now get is: Test template 4.obj : error LNK2019: unresolved external symbol "public: __thiscall A::~A(void)" (??1?$A@H@@QAE@XZ) referenced in function _wmain


Solution

  • Explicit specializations are the way to go.

    I assume that you are including your A.h in several .cpp, and that's the root cause of your problem.

    Specializations are definitions and there must be only one definition of A::A() and A::A() and so they must be in only one .cpp.

    You'll have to move the explicit specialization in a .cpp

    template <>
    A<int>::A()
    {
        data = 1;
    }
    template <>
    A<std::string>::A()
    {
        data = "one";
    }
    

    and keep a declaration for them in A.h

    template<> A<int>::A();
    template<> A<std::string>::A();
    

    so that the compiler knows they are explicitly specialized and doesn't try to add automatic one.

    Edit: with these four files, g++ m.cpp f.cpp a.cpp doesn't show any errors.

    // a.h
    #define A_H
    
    #include <string>
    
    template <class T>
    class A
    {
    private:
        T data;
    public:
        A();
    };
    
    template<> A<int>::A();
    template<> A<std::string>::A();
    
    #endif
    
    // a.cpp
    #include "a.h"
    
    template <>
    A<int>::A()
    {
        data = 1;
    }
    template <>
    A<std::string>::A()
    {
        data = "one";
    }
    
    // f.cpp
    #include "a.h"
    
    int f()
    {
        A<int> one;
        A<std::string> two;
    }
    
    // m.cpp
    #include "a.h"
    
    int f();
    
    int main()
    {
        A<int> one;
        A<std::string> two;
        f();
    }