templates and string literals and UNICODE

2020-02-11 06:49发布

NEW: Thank you everyone who helped me with this! The answer is marked below, and I've expanded on the answer with a functioning version in my question, below (q.v.):


I seem to be running into this situation a lot (while updating our string utilities library):

I need a way to have a template which works for both char and wchar_t, which uses various string-literals. Currently I'm finding this challenging because I don't know how to have a compile-time way to alter string literals to be narrow or wide character.

For consideration, take the following TCHAR based function:

// quote the given string in-place using the given quote character
inline void MakeQuoted(CString & str, TCHAR chQuote = _T('"'))
{
    if (str.IsEmpty() || str[0] != chQuote)
        str.Format(_T("%c%s%c"), chQuote, str, chQuote);
}

I want to template it instead:

// quote the given string in-place using the given quote character
template <typename CSTRING_T, typename CHAR_T>
inline void MakeQuoted(CSTRING_T & str, CHAR_T chQuote = '"')
{
    if (str.IsEmpty() || str[0] != chQuote)
        str.Format("%c%s%c", chQuote, str, chQuote);
}

Immediately we have a problem with the two string literals ('"', and "%c%s%c").

If the above is invoked for CSTRING_T = CStringA, CHAR_T = char, then the above literals are fine. But if it is invoked for CStringW and wchar_t, then I really need (L'"', and L"%c%c%c").

So I need some way to do something like:

template <typename CSTRING_T, typename CHAR_T>
inline void MakeQuoted(CSTRING_T & str, CHAR_T chQuote = Literal<CHAR_T>('"'))
{
    if (str.IsEmpty() || str[0] != chQuote)
        str.Format(Literal<CHAR_T>("%c%s%c"), chQuote, str, chQuote);
}

And that's where I am lost: What in the world can I do to make Literal(string-or-character-literal) that actually results in L"string" or "string" depending on CHAR_T?

Edit: There are over a hundred functions, many of them more complex with more string-literals in them, that need to be available both for narrow and wide strings. Short of copying every such function and then editing each one to either be wide or narrow, surely there is a technique that would allow a single definition that varies by CHAR_T?


I'm giving the answer to the hybrid macro + template that Mark Ransom supplied, but I wanted to include a more complete solution (for anyone who cared), so here it is:

// we supply a few helper constructs to make templates easier to write
// this is sort of the dark underbelly of template writing
// to help make the c++ compiler slightly less obnoxious

// generates the narrow or wide character literal depending on T
// usage: LITERAL(charT, "literal text") or LITERAL(charT, 'c')
#define LITERAL(T,x) template_details::literal_traits<typename T>::choose(x, L##x)

namespace template_details {

    // Literal Traits uses template specialization to achieve templated narrow or wide character literals for templates
    // the idea came from me (Steven S. Wolf), and the implementation from Mark Ransom on stackoverflow (http://stackoverflow.com/questions/4261673/templates-and-string-literals-and-unicode)
    template<typename T>
    struct literal_traits
    {
        typedef char char_type;
        static const char * choose(const char * narrow, const wchar_t * wide) { return narrow; }
        static char choose(const char narrow, const wchar_t wide) { return narrow; }
    };

    template<>
    struct literal_traits<wchar_t>
    {
        typedef wchar_t char_type;
        static const wchar_t * choose(const char * narrow, const wchar_t * wide) { return wide; }
        static wchar_t choose(const char narrow, const wchar_t wide) { return wide; }
    };

} // template_details

In addition, I created some helpers to make writing templates that utilized this concept in conjunction with CStringT<> a bit easier / nicer to read & comprehend:

// generates the correct CString type based on char_T
template <typename charT>
struct cstring_type
{
    //  typedef CStringT< charT, ATL::StrTraitATL< charT, ATL::ChTraitsCRT< charT > > > type;
    // generate a compile time error if we're invoked on a charT that doesn't make sense
};

template <>
struct cstring_type<char>
{
    typedef CStringA type;
};

template <>
struct cstring_type<wchar_t>
{
    typedef CStringW type;
};

#define CSTRINGTYPE(T) typename cstring_type<T>::type

// returns an instance of a CStringA or CStringW based on the given char_T
template <typename charT>
inline CSTRINGTYPE(charT) make_cstring(const charT * psz)
{
    return psz;
}

// generates the character type of a given CStringT<>
#define CSTRINGCHAR(T) typename T::XCHAR

With the above, it is possible to write templates which generate the correct CString variety based on CStringT<> or char/wchar_t arguments. For example:

// quote the given string in-place using the given quote character
template <typename cstringT>
inline void MakeQuoted(cstringT & str, CSTRINGCHAR(cstringT) chQuote = LITERAL(CSTRINGCHAR(cstringT), '"'))
{
    if (str.IsEmpty() || str[0] != chQuote)
        str.Format(LITERAL(cstringT::XCHAR, "%c%s%c"), chQuote, str, chQuote);
}

// return a quoted version of the given string
template <typename cstringT>
inline cstringT GetQuoted(cstringT str, CSTRINGCHAR(cstringT) chQuote = LITERAL(CSTRINGCHAR(cstringT), '"'))
{
    MakeQuoted(str, chQuote);
    return str;
}

7条回答
姐就是有狂的资本
2楼-- · 2020-02-11 07:32

I have a similar situation. I have made 1 source-code file and a header-file (of course) which I exclude from building. Then created 2 other source-files which contain the original source via an #include directive. In one file I #define UNICODE (if not already defined) before the include. In the other file I #undef UNICODE (if defined). the source file contains a few static structures and a number of functions ,which are identical (in text) for both sets of char (not when compiled). If every function has either wchar_t or char as a parameter results this method in 2 sets of overloaded functions or 2 sets of differently named functions (depends on how the header file is written ,take tchar.h as example). Now both UNICODE and ANSI versions of the functions are available for an application and if the header-file is correctly written also default version for TCHAR. If you wish I can ellaborated on it ,just say so.

查看更多
登录 后发表回答