tags:

views:

147

answers:

4

Consider this code:

template <typename T>
class String
{
public:
    ...
 String(T* initStr)
 {
  size_t initStrLen;
  if (initStr != NULL)
  {
   printf_s("%s\n", typeid(T) == typeid(char) ? "char" : "wchar_t");

   if (typeid(T) == typeid(char))
   {
    strlen((T*)initStr);
   }
   else if (typeid(T) == typeid(wchar_t))
   {
    wcslen((T*)initStr);
   }
  }
 }  
    ...
};

When I compiled the code, I got this error message:

...\main.cpp(32) : error C2664: 'strlen' : cannot convert parameter 1 from 'wchar_t *' to 'const char *'

Then I tried to use a function pointer:

typedef size_t (*STRLEN)(void*);
STRLEN _strlen;
_strlen = reinterpret_cast<STRLEN> (typeid(*initStr) == typeid(char) ? strlen : wcslen);

and again the compiler issued an error, this time:

...\main.cpp(28) : error C2446: ':' : no conversion from 'size_t (__cdecl *)(const wchar_t *)' to 'size_t (__cdecl *)(const char *)'

My question is, how can I use the functions strlen and wcslen with templates?

+5  A: 

You can't use if statements to control what code is instantiated for a template: all of the code in the body must work for every instantiation.

std::size_t strlen(wchar_t const *s) {
  return std::wcslen(s);
}

//...
String(T* initStr) {
  using std::strlen;  // bring into scope so unqualified call can find it
  std::size_t length = strlen(initStr);  // uses std::strlen or our strlen
  //...

You could also add an overload of your strlen for char, then you don't need the using declaration.

Roger Pate
+3  A: 

You have misunderstood templates. You should not use typeid to determine types here, but instead use template specialisation.

Peter Alexander
Yes, but would be nice to give a coded example. vitaut used a different approach, with an overloaded function.
CashCow
My first thought was a specialisation too, but here, specialisation in practice means writing 2 versions of the class, which can be done without templates at all.
Maciej Hehl
+8  A: 

You can do this e.g. by introducing a helper function as illustrated below:

#include <iostream>
#include <string.h>

size_t GetLength(const char* s) { return strlen(s); }
size_t GetLength(const wchar_t* s) { return wcslen(s); }

template <typename T>
void PrintLength(T s)
{
    std::cout << GetLength(s) << std::endl;
}

int main()
{
    PrintLength("abc");
    PrintLength(L"abc");
}

Use this helper function GetLength instead of strlen or wcslen and don't check the type of the argument explicitly. You can write overloads of GetLength for other types as well, e.g. std::string.

You rarely need to use typeid in practice and in this case it is completely inappropriate.

vitaut
Right idea, just of course PrintLength(const T* s)
CashCow
+1  A: 

In case the OP is interested in how strings are implemented in STL, they use a whole helper class call char_traits. This is a class with nothing but static member functions, and char_traits is specialised for char and wchar_t to use the C runtime library functions like memmove.

For example you have a compare function that returns a value <0, 0 or >0. Where the type is char it can use memcmp. Where the type is wchar_t it can use the wide equivalent.

It works something like this:

template< typename Element >
class char_traits
{
public:
   static int compare( const Element * left, const Element * right, size_t length )
   {
       for( const Element * end = left + length; left != end; ++left )
       {
          if( left < right )
             return -1;
          else if( left > right )
              return 1;
       }
       return 0;
   }
  // other functions
};

template <> class char_traits<char> // I think this is the syntax
{
public:
   int compare( const char * left, const char * right, size_t len )
   {
     return memcmp( left, right, len ); // more efficient than the general loop above
   }
  // other functions
};

// specialise also for wchar_t
CashCow