views:

74

answers:

4

I'm writing a logging class that uses a templatized operator<< function. I'm specializing the template function on wide-character string so that I can do some wide-to-narrow translation before writing the log message. I can't get TCHAR to work properly - it doesn't use the specialization. Ideas?

Here's the pertinent code:

// Log.h header
class Log
{
  public:
    template <typename T> Log& operator<<( const T& x );

    template <typename T> Log& operator<<( const T* x );

    template <typename T> Log& operator<<( const T*& x );

    ... 
}

template <typename T> Log& Log::operator<<( const T& input )
{ printf("ref"); }

template <typename T> Log& Log::operator<<( const T* input )
{ printf("ptr"); }

template <> Log& Log::operator<<( const std::wstring& input );
template <> Log& Log::operator<<( const wchar_t* input );

And the source file

// Log.cpp 
template <> Log& Log::operator<<( const std::wstring& input )
{ printf("wstring ref"); }
template <> Log& Log::operator<<( const wchar_t* input )
{ printf("wchar_t ptr"); }
template <> Log& Log::operator<<( const TCHAR*& input )
{ printf("tchar ptr ref"); }

Now, I use the following test program to exercise these functions

// main.cpp - test program
int main()
{
 Log log;
 log << "test 1";
 log << L"test 2";
 std::string test3( "test3" );
 log << test3;
 std::wstring test4( L"test4" );
 log << test4;
 TCHAR* test5 = L"test5";
 log << test5;
}

Running the above tests reveals the following:

// Test results
ptr
wchar_t ptr
ref
wstring ref
ref

Unfortunately, that's not quite right. I'd really like the last one to be "TCHAR", so that I can convert it. According to Visual Studio's debugger, the when I step in to the function being called in test 5, the type is wchar_t*& - but it's not calling the appropriate specialization. Ideas?

I'm not sure if it's pertinent or not, but this is on a Windows CE 5.0 device.

+5  A: 

TCHAR is a macro that references a typedef either for wchar_t or for char. By the time you're instantiating templates, the macro has already been substituted. You're going to end up referencing either your template instance for char or the one for wchar_t.

jwismar
Thanks, but I know which function is getting called. What I'd like to know is how I can specialize my template for TCHAR*, since right now it's not calling my other specializations.
Eli
I don't think you understand... TCHAR isn't a type. The templates have no idea what TCHAR is once the preprocessor is done. All the templates see is either char or wchar_t.
Cogwheel - Matthew Orlando
fair enough - thanks. Replace all of the TCHARs with wchar_t. Results are still the same. In other words, if I change test5 from TCHAR to wchar_t, it doesn't call my wchar_t specialization.
Eli
@Eli: See my answer for explanations.
AndreyT
+1  A: 

(What @jwismar says is correct, but that is not the main problem with the code).

Consider this piece of code

void foo(const int*& p);

int main() {
  int *p = 0;
  foo(p); // ERROR: cannot initialize `const int *&` with `int *`
}

If you try to compile it, it will result in an error. The reason for the error is that it is impossible to bind a reference of const int *& type to a pointer of int * type. This would violate the const-correctness rules.

Another, smaller example that illustrates the same problem

int *p = 0;
const int *&rp = p; // ERROR: cannot initialize `const int *&` with `int *`

This is actually the problem you have in your code as well. You declared your template with const TCHAR*& parameter type

template <> Log& Log::operator<<( const TCHAR*& input )
{ printf("tchar ptr ref"); }

and expect it to be called for argument of TCHAR * type

TCHAR* test5 = L"test5";
log << test5;

It is impossible. Your template is not considered as a viable function for the call. Either add const to the argument type, or remove const from the parameter type. Or, maybe, get rid of the reference. (Why do you declare the parameter as a reference to a pointer?)

P.S. You might find it surprising that instead of your desired target function the compiler calls the template

template <typename T> Log& Log::operator<<( const T& input )
{ printf("ref"); }

At the first sight it might seem as if the compiler is doing what I just called "impossible". In reality, what the compiler is doing is a completely different thing.

The latter template gets instantiated with T equal to TCHAR *. How, if you carefully expand the above argument declaration for T == TCHAR *, you'll get TCHAR *const &, not const TCHAR *&. These are two completely different things. It is perfectly possible to initialize a TCHAR *const & reference with a TCHAR * pointer, which is exactly what the compiler is doing in your case.

Returning to my simple example

int *p = 0;
const int *&rp1 = p; // ERROR: cannot initialize `const int *&` with `int *`
int *const &rp2 = p; // OK, no error here
AndreyT
Thanks for the in-depth explanation. I went with a const reference because I figured that the compiler would detect the wchar_t* as a pointer, look for a matching function, possibly find the const-reference version, and use that. i wanted to try to prevent that.
Eli
Wait a second, you're not quite right there. Test5 is matching a function that requires a const parameter. So, I'm not breaking const-correctness, because it does work. My problem is related to the order of precedence for evaluating which overloaded template function is being called.
Eli
AndreyT
Mark Ransom
Thanks guys, I got it sorted out just as you were posting these :)
Eli
A: 

A-HA!

So, I broke the problem down into it's smallest part and discovered something about the order in which template functions are matched. First, I broke the program down into this:

// main.cpp
int main()
{
 Log log;
 wchar_t* test = L"test";
 log << test;
 return 0;
}

And // Log.h

class Log
{
  public:
   template <typename T> Log& operator<<( const T* x );
};

template <> Log& Log::operator<<( wchar_t* const & input );

And // Log.ccp

#include "Log.h"

template <> Log& Log::operator<<( const wchar_t* input )
{ printf("wchar_t ptr\n"); return *this; }

And sure enough, my template specialization got called. When I added another specialization to the Log header like this

template <typename T> Log& operator<<( const T& x );

The compiler started matching the new function instead. This time, however, I didn't include a definition for the template, so the linker complained. The linker showed the following error:

error LNK2019: unresolved external symbol "public: class Log & __thiscall Log::operator<<<wchar_t *>(wchar_t * const &)" (??$?6PA_W@Log@@QAEAAV0@ABQA_W@Z) referenced in function _main

This told me the type it was trying to match:

wchar_t* const &

A const pointer, not a pointer-to-const reference! So, now my program works. I just specialize the template like so:

template <> Log& Log::operator<<( wchar_t* const & input );

Thanks for the help everyone.

Eli
A: 

I think that the lack of constness is confusing the compiler.

Try

        const TCHAR* test5 = L"test5";

or even more correctly

        const TCHAR* test5 = _T("test5");

I think that will give you what you expect.

Michael J