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开发者_StackOverflow "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.
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.
(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
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.
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.
精彩评论