【问题标题】:templates and string literals and UNICODE模板和字符串文字和 UNICODE
【发布时间】:2011-05-14 18:49:00
【问题描述】:

新:感谢所有帮助我的人!答案标记在下面,我在下面(q.v.)的问题中使用功能版本扩展了答案:


我似乎经常遇到这种情况(在更新我们的字符串实用程序库时):

我需要一种方法来创建一个适用于 char 和 wchar_t 的模板,该模板使用各种字符串文字。目前我发现这很有挑战性,因为我不知道如何使用编译时方法将字符串文字更改为窄字符或宽字符。

考虑一下,采用以下基于 TCHAR 的函数:

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

我想改为模板:

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

我们马上就遇到了两个字符串文字('"' 和 "%c%s%c")的问题。

如果为 CSTRING_T = CStringA, CHAR_T = char 调用上述内容,则上述文字没问题。但是如果它是为CStringW和wchar_t调用的,那我真的需要(L'"', and L"%c%c%c")。

所以我需要一些方法来做类似的事情:

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

这就是我迷失的地方:我该怎么做才能根据 CHAR_T 生成 L"string" 或 "string" 的 Literal(string-or-character-literal)?

编辑:有超过一百个函数,其中许多更复杂,其中包含更多字符串文字,需要同时适用于窄字符串和宽字符串。除了复制每个这样的函数,然后将每个函数编辑为宽或窄,肯定有一种技术可以允许单个定义随 CHAR_T 变化吗?


我正在回答 Mark Ransom 提供的混合宏 + 模板,但我想包含一个更完整的解决方案(对于任何关心的人),所以这里是:

// 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

此外,我创建了一些帮助程序,以使将这个概念与 CStringT 结合使用的编写模板更容易/更好地阅读和理解:

// 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

通过以上内容,可以编写基于 CStringT 或 char/wchar_t 参数生成正确 CString 种类的模板。例如:

// 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;
}

【问题讨论】:

  • 我知道我可以使用函数重载为 MakeQuoted 生成两个定义,而不是使用单个模板,此时我不需要担心文字(请参阅下面的 @In硅的回答)。但是,考虑到我逐字重复所有代码只是为了提供两组不同的文字,这似乎很愚蠢。肯定有某种方法可以使用元编程即时生成正确的文字(依赖于类型的文字)?
  • @Mordachai:不使用元编程。如果有的话,我们也可以打开字符串。你能举一个你想用来使用这个函数的语法的例子吗?
  • 我不确定是不是这样。模板生成的代码根据提供给模板的类型参数而变化。我在这里寻找的是一个随提供给它的类型而变化的文字。也许模板专业化?!就像我希望编译器对字符串文字进行自动类型提升(想想标量参数,模板会毫不费力地工作,因为标量文字会自动提升为适当的类型。但对于字符串文字则不然)。 :(
  • @Mordachai:不能将const char* 升级为const wchar_t* 或反之亦然的原因是,就编译器而言,它们是完全不同的类型。
  • 慢动作配对编程 - 我喜欢它。

标签: c++ visual-studio templates unicode mfc


【解决方案1】:

概念是使用宏来生成两种形式的文字,charwchar_t,然后让模板函数选择适合上下文的一种。

请记住,模板函数实际上不会生成任何代码,除非您有其他代码调用它们。大多数时候这无关紧要,但对于图书馆来说却是这样。

此代码未经测试,但我相信它会起作用。

#define LITERAL(T,x) CString_traits<T>::choose(x, L##x)

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

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

template <typename T>
inline void MakeQuoted(T & str, CString_traits<T>::char_type chQuote = LITERAL(T,'"'))
{
    if (str.IsEmpty() || str[0] != chQuote)
        str.Format(LITERAL(T,"%c%s%c"), chQuote, str, chQuote);
}

【讨论】:

  • 我可以看到的主要问题是,如果调用者想要替换 chQuote - 他们必须暴露于 LITCHAR 宏,从这个角度来看它并不是非常干净。跨度>
  • @DeadMG,我没有看到任何方法可以避免将调用代码暴露给宏。有必要同时创建文字的 char 和 wchar_t 形式,而不是重复自己。
  • 无论如何我都不是语言纯粹主义者。我想要高度可用的代码,这看起来很有希望。我会试一试,看看我能不能让它工作。在成千上万的世界中不得不使用一个额外的宏(Win32 编程)根本不是坏事。
【解决方案2】:

这篇文章是我个人的一点点天才。

#include <malloc.h>
template<typename to, int size> to* make_stack_temporary(const char(&lit)[size], to* memory = (to*)_alloca(sizeof(to)*size)) {
    for(int i = 0; i < size; i++)
        memory[i] = lit[i];
    return memory;
}

当您在默认参数中使用 alloca 时,它实际上是从 调用者 的堆栈中分配的,允许您返回数组而无需求助于堆。没有动态分配,没有内存释放。 _alloca 是 MSVC 提供的 CRT 函数,所以我不提供任何可移植性保证 - 但如果您使用 ATL,那无论如何都可能没有问题。当然,这也意味着指针不能通过调用函数,但对于格式字符串等临时用途来说应该足够了。还有一些与您不太可能遇到的异常处理有关的警告(有关详细信息,请查看 MSDN),当然,它仅适用于具有相同二进制表示的字符,据我所知,这是您可以使用的每个字符放入一个窄字符串文字。我很欣赏这只能解决您可能遇到的实际问题的一个子集,但它比宏或指定每个文字两次等特定子集的解决方案要好得多。

您还可以使用绝对丑陋但行为更一致的聚合初始化。

template<typename T> some_type some_func() {
    static const T array[] = { 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', ' ', 'l', 'i', 't', 'e', 'r', 'a', 'l', '\0' };
}

在带有可变参数模板的 C++0x 中,这种解决方案可能不会很糟糕。我接近更好的解决方案,即 C++03,但不要屏住呼吸。

编辑:你可以这样做,imo 是最好的解决方案,仍然涉及一些混乱。

#include <iostream>
#include <array>
#include <string>

struct something {
    static const char ref[];
};

const char something::ref[] = "";

template<int N, const char(*t_ref)[N], typename to> struct to_literal {
private:
    static to hidden[N];
public:
    to_literal() 
    : ref(hidden) {
        for(int i = 0; i < N; i++)
            hidden[i] = (*t_ref)[i];
    }
    const to(&ref)[N];
};
template<int N, const char(*t_ref)[N], typename to> to to_literal<N, t_ref, to>::hidden[];

template<int N, const char(&ref)[N], typename to> const to* make_literal() {
    return to_literal<N, &ref, to>().ref;
}

int main() {
    std::wcout << make_literal<sizeof(something::ref), something::ref, wchar_t>();
    std::wcin.get();
}

你必须遍历每一个字面量并使其成为结构的静态成员,然后引用它,但它的效果要好得多。

【讨论】:

  • @Mordachai:我也认为语言在这方面很差,可以大大改进。
【解决方案3】:

考虑到只有两种使用 MakeQuoted() 的方法,您不需要为此类事情使用模板。您可以将函数重载用于相同目的:

inline void MakeQuoted(CStringA& str, char chQuote = '"') 
{ 
    if (str.IsEmpty() || str[0] != chQuote) 
        str.Format("%c%s%c", chQuote, str, chQuote); 
} 


inline void MakeQuoted(CStringW& str, wchar_t chQuote = L'"') 
{ 
    if (str.IsEmpty() || str[0] != chQuote) 
        str.Format(L"%c%s%c", chQuote, str, chQuote); 
} 

这当然是最简单的方法,无需使用宏,假设这是您尝试使用字符串实用程序库的基于模板的解决方案的原因。

您可以为长而复杂的功能分解出常用功能:

template<typename CStrT, typename CharT>
inline void MakeQuotedImpl(CStrT& str, CharT chQuote,
    const CharT* literal)
{
    if (str.IsEmpty() || str[0] != chQuote) 
        str.Format(literal, chQuote, str, chQuote); 

}

inline void MakeQuoted(CStringA& str, char chQuote = '"') 
{ 
    MakeQuotedImpl(str, chQuote, "%c%s%c");
} 


inline void MakeQuoted(CStringW& str, wchar_t chQuote = L'"') 
{
    MakeQuotedImpl(str, chQuote, L"%c%s%c");
} 

【讨论】:

  • 这正是我目前正在做的事情。它的扩展性不是很好(我们有一百多个类似于上面的函数)。
  • @Mordachai:根据您对 John Dibling 的回答的评论,您似乎想要创建一个库,该库可以在单个项目中使用基于 charwchar_t 的字符串,而不考虑编译器设置。不幸的是,你可以做的不多了——函数重载是这种特殊情况下“最简单”的方法。我知道 Windows API 对每个接受字符串的函数都有两个版本(例如 CreateWindowA()CreateWindowW())。
  • @Mordachai:请注意,您可能不需要对字符串实用程序库中的每个函数都使用函数重载。如果您确实有可以“转换”为模板函数而不会出现此类问题的函数,那么您当然可以为这些函数执行此操作。
  • 只要没有文字,我就可以使用模板。一旦有文字,我就不得不求助于函数重载(或者有时我可以分解出一个模板化所有必要参数的模板,然后创建另外两个函数,即 char/wchar_t,并提供所需的参数和适当的底层模板版本的文字)
  • @Mordachai:这可能是最好的方法,尤其是对于更长、更复杂的功能。查看我的编辑。
【解决方案4】:

我也有类似的情况。我已经制作了 1 个源代码文件和一个头文件(当然),我将它们排除在构建之外。然后通过#include 指令创建了另外两个包含原始源的源文件。在一个文件中,我在包含之前#define UNICODE(如果尚未定义)。在另一个文件中,我 #undef UNICODE(如果已定义)。源文件包含一些静态结构和一些函数,它们对于两组 char 都是相同的(在文本中)(不是在编译时)。如果每个函数都有 wchar_t 或 char 作为参数,则此方法会导致 2 组重载函数或 2 组不同命名的函数(取决于头文件的编写方式,以 tchar.h 为例)。现在,函数的 UNICODE 和 ANSI 版本都可用于应用程序,如果头文件正确编写,TCHAR 的默认版本也是如此。如果你希望我能详细说明,就这么说吧。

【讨论】:

  • 包装技巧也可以用于头文件,但要注意重新定义或重新定义 UNICODE。
  • 我想过这样做。我想只要你设置了_T、TEXT 和UNICODE,就足以满足我的需要了。但是,如果您尝试调用诸如 _ttoi 或 _tcscpy 之类的东西,这些之前不会被定义为 _mbs 或 str - CRT 标头不使用 #pragma 一次吗?
  • 我自己没有使用 CRT 例程,而是使用 WINDOWS API(以避免多线程问题)。但是您应该尝试一下,如果它有效,您可以为自己节省大量工作和调试。但是请确保不要使用预编译的头文件,因为这将阻止此技术工作。仅用于包装源,您的其他源可以使用 pp。
  • 我认为#pragma once 意味着一个标头在编译一个特定源的过程中只包含一次,因为我认为你不会遇到麻烦。
  • 我还提交了一个关于这种技术的问题:“一个源具有多个对象”
【解决方案5】:

我相信你想要TEXT MFC 宏:

TCHAR* psz = TEXT("Hello, generic string");

【讨论】:

  • 如果我回答了你没有问的问题,请告诉我,我会删除它
  • 如果我需要的是一个随编译器设置而不是正在处理的字符串类型而变化的字符串,则此方法有效。这非常适用于客户端代码(应用程序源代码),但对于在单个项目中用于窄字符串和宽字符串的库代码没有帮助,无论编译器的 UNICODE 或 MBCS 构建目标如何。
【解决方案6】:

您可以对 MarkQuoted 使用模板偏特化,并根据类型引用。

【讨论】:

    【解决方案7】:

    好的,所以,如果您真的想对此进行模板化,我认为我能想到的最好的方法是基于this discussion 存储您的文字的模板化类。像这样的:

    template <typename T> class Literal;
    template <> class Literal<char>
    {
    public:
        static const char Quote = '"';
    };
    template <> class Literal<wchar_t>
    {
    public:
        static const wchar_t Quote = L'"';
    };
    

    然后,您将在非专业但模板化的函数中使用Literal&lt;CHAR_T&gt;::Quote。我猜有点乱,但它的好处是让你的函数逻辑不重复,并为你提供模板化的字符串文字。

    【讨论】:

    • 与上述相同的答案:这因编译器构建目标而异,而不是通过传入的字符串类型。我的项目必须在同一个版本中同时使用窄字符串和宽字符串(对于 Win32 API 来说,窄和宽的旧文件等)
    • 谢谢 - 我考虑过将其作为一个策略类来做。只要我愿意将各种任意字符串放入其中以供使用,它就可以工作(我可以将其放入详细信息命名空间中,仅用于该标题...)。
    猜你喜欢
    • 2016-06-08
    • 1970-01-01
    • 2014-11-25
    • 1970-01-01
    • 2017-02-14
    • 2021-03-30
    • 1970-01-01
    • 2018-07-05
    • 1970-01-01
    相关资源
    最近更新 更多