шаблоны и строковые литералы и UNICODE - PullRequest
4 голосов
/ 24 ноября 2010

NEW: Спасибо всем, кто помог мне с этим!Ответ помечен ниже, и я расширил ответ с функционирующей версией в своем вопросе ниже (qv):


Я, кажется, сталкиваюсь с этой ситуацией много (при обновлении нашегобиблиотека утилит строки):

Мне нужен способ иметь шаблон, который работает как для 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 '"' и 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);
}

И вот где я потерян: что в мире я могу сделать, чтобы сделать Literal(строка-или-символ-литерал), которая фактически приводит к L "строка" или "строка" в зависимости от CHAR_T?

Редактировать: Есть более ста функций, многие из которых более сложные с большим количеством строковых литераловв них это должно быть доступно как для узких, так и для широких струн.Если не считать копирование каждой такой функции, а затем редактирование каждой из них, чтобы она была либо широкой, либо узкой, безусловно, существует метод, который позволил бы получить одно определение, которое варьируется в зависимости от CHAR_T?


Я даю ответгибридный макрос + шаблон, предоставленный Марком Рэнсомом, но я хотел включить более полное решение (для всех, кому не все равно), поэтому вот оно:

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

С учетом вышеизложенного можно писать шаблоны, которые генерируют правильное разнообразие CString на основе CStringT <> или аргументы char / wchar_t.Например:

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

5 голосов
/ 24 ноября 2010

Идея состоит в том, чтобы использовать макрос для генерации обеих форм литерала, char и wchar_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);
}
1 голос
/ 24 ноября 2010

Этот кусок - моя личная маленькая гениальность.

#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 в аргументе по умолчанию, он фактически выделяется из стека caller , что позволяетвам возвращать массивы, не прибегая к куче.Нет динамического выделения, нет освобождения памяти._alloca - это функция CRT, предоставляемая MSVC, поэтому я не даю никаких гарантий переносимости, но если вы используете 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();
}

Вы должны пройти через каждый литерал и сделать его статическим членом структуры, а затем ссылаться на него, но он работает намного лучше.

0 голосов
/ 24 ноября 2010

У меня похожая ситуация. Я сделал 1 файл исходного кода и файл заголовка (конечно), который исключаю из сборки. Затем создал 2 других исходных файла, которые содержат исходный источник с помощью директивы #include. В одном файле я #define UNICODE (если еще не определен) перед включением. В другом файле я #undef UNICODE (если определен). исходный файл содержит несколько статических структур и ряд функций, которые идентичны (в тексте) для обоих наборов символов (не при компиляции). Если каждая функция имеет wchar_t или char в качестве параметра, результат этого метода в 2 наборах перегруженных функций или 2 наборах функций с разными именами (в зависимости от того, как записан файл заголовка, возьмем tchar.h в качестве примера). Теперь для приложения доступны версии функций как UNICODE, так и ANSI, и, если заголовочный файл записан правильно, также версия по умолчанию для TCHAR. Если хочешь, я могу это проработать, просто скажи.

0 голосов
/ 24 ноября 2010

Вы можете использовать частичную специализацию шаблона для MarkQuoted и цитировать на основе типа.

0 голосов
/ 24 ноября 2010

Хорошо, так что, если вы действительно хотите создать шаблон, я думаю, что лучшее, что я смог придумать, это шаблонный класс, который хранит ваши литералы, основываясь на этом обсуждении .Примерно так:

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<CHAR_T>::Quote в своих неспециализированных, но шаблонных функциях.Я думаю, что это немного грязно, но преимущество состоит в том, что вы не дублируете логику вашей функции и получаете шаблонные строковые литералы.

0 голосов
/ 24 ноября 2010

Вам не нужно использовать шаблоны для чего-то подобного, учитывая, что есть только два способа использования 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");
} 
0 голосов
/ 24 ноября 2010

Полагаю, вам нужен макрос TEXT MFC:

TCHAR* psz = TEXT("Hello, generic string");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...