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

StackOverflow https://stackoverflow.com/questions/11325478

  •  19-06-2021
  •  | 
  •  

سؤال

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

هل كانت مفيدة؟

المحلول

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();
}

نصائح أخرى

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

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() {}
};
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top