2

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

Community
  • 1
  • 1
physicalattraction
  • 6,485
  • 10
  • 63
  • 122
  • 1
    1) Don't use `_tmain` crap and friends. 2) What's the point of using `typeid` with templates? The code simply won't compile for `T = int`, because there is no `int::operator=(const char *)`... 3) Testcase for that compile error, please. – Griwes Jul 04 '12 at 08:44
  • 1
    Template specialization you did as the second example is the way to go IMHO..did you add inclusion guards to your header file? From linker error it looks like you didn't. – Naveen Jul 04 '12 at 08:44
  • I think you could use SFINAE for this. With `enable_if` and `is_same` you could have different member funcs based on template params. – jrok Jul 04 '12 at 09:01
  • @jrok: I have never heard of SFINAE, but I will have a look at it. – physicalattraction Jul 05 '12 at 09:35
  • @Naveen: I do have inclusion guards in all my header files, so that cannot be the cause. – physicalattraction Jul 05 '12 at 09:35
  • @Griwes: 1. _tmain is standard in Visual Studio 2010, I don't change this. 2. The question was how to avoid this. 3. It's given in the question (see main.cpp) – physicalattraction Jul 05 '12 at 09:35
  • @physicalattraction, that doesn't make _tmain not-a-crap; it can resolve to `wmain`, which is additional crap on its own... And testcase is minimal code example, pasted on site like ideone, and linked to result... not random pieces of code in a question. – Griwes Jul 05 '12 at 10:41

3 Answers3

3

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();
}
AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • Thanks for your answer. I don't get it working yet though. You say you place the declarations in the header file, but do you place it in the class? If I place it inside the class, the compiler says: error C2931: 'A' : template-class-id redefined as a member function of 'A'. If I place it outside the class, the linker says: error LNK2005: "public: __thiscall A::A(int,int)" (??0?$A@H@@QAE@HH@Z) already defined in A.obj – physicalattraction Jul 05 '12 at 09:26
  • That works, is clear, and is exactly what I am looking for! :-D – physicalattraction Jul 05 '12 at 10:13
3

You are correct in the second solution, what you need is template specialisation (keeping declaration and implementation together):

#include <string>

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

template <>
class A <std::string>
{
private:
  std::string data;
public:
  A() { data = "one"; }
};

template <>
class A <int>
{
private:
  int data;
public:
  A() { data = 1; }
};

If I may suggest a more elegant solution, then I would add a parameter to the constructor and avoid the template specialisation:

template <class T>
class A
{
private:
    T data;
public:
    A( T value ) : data( value ) {}
    virtual ~A() {}
};
Sdra
  • 2,297
  • 17
  • 30
  • Thanks for your answer. Regarding your first solution: how can I call generic methods on the specialized classes? More specific: I have added a GetData() method in the generic class and typed in main: A number(); cout << A.GetData(); The compiler then gives error C2039: 'GetData' is not a member of 'A'. How can I solve this? Regarding your second solution: that is very elegant. My real problem is a bit more complicated though, the field data is initialized by calling a method depending on the input type. That is why I wanted to use template specialization. – physicalattraction Jul 05 '12 at 09:54
  • 1
    When using templates with classes, the compiler generates a completely different class for each specialization. Generic template class and specialized template class are not like base class and derived class. Therefore you need to reimplement the GetData() method in each specialization, unless you use the second solution. – Sdra Jul 05 '12 at 13:26
2

In case it's just the c'tor where you want to have behavior that depends on T, I would suggest to factor this out to a different struct:

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 "1"; }
}

If you specialize a class on it's template parameter, the different specializations are totally different types and can have different sets of data and functions.

Finally:

template <class T>
A<T>::A()
  : data( initial_data< T >::data() ) 
{
}

kind regards Torsten

Torsten Robitzki
  • 3,041
  • 1
  • 21
  • 35
  • Thanks for your answer. I have the above code in the header file now (including semicolons after the structs). I placed it after the class definition. With the above main I still get linker errors LNK 2019. Does this code work for you? Do I have the order right? – physicalattraction Jul 05 '12 at 09:42
  • @physicalattraction If you had the order wrong, the compiler would complain. LNK2019 is a linker error that complains about a missing external symbol. Maybe you can provide us with a very small example that reproduces the problem including the exact wording of the error message. – Torsten Robitzki Jul 05 '12 at 09:54
  • I have edited the opening post with the code as I have it right now. I don't see any mistake. Maybe you can see it? – physicalattraction Jul 05 '12 at 10:00
  • @physicalattraction You declared a destructor for A, but didn't defined one. And that's what the linker is complaining about. Add a definition for the destructor or delete the declaration :-) – Torsten Robitzki Jul 05 '12 at 10:06
  • That works! I now have the complete code running with both your and AProgrammer's solution. – physicalattraction Jul 05 '12 at 10:16