Бен Ки: [email protected]

1 октября 2013; 10 ноября 2018 г.

В этой статье представлено несколько методов, которые можно использовать при создании функций шаблона, в которых тип символа является параметром шаблона, и вы должны использовать символьные или строковые константы. Он также предоставляет исходный код для нескольких шаблонных функций и макросов, которые можно использовать для реализации одного из предлагаемых решений.

описание проблемы

Существуют различные типы данных, которые могут использоваться для представления символов в C++. Наиболее распространенными из них являются char и wchar_t. Часто бывает необходимо написать код, способный обрабатывать символы любого типа.

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

Рассмотрим следующий пример.

Функция CommandLineToArgVector анализирует строку командной строки, например ту, которая может быть возвращена функцией GetCommandLine. Эта функция зависит от ряда символьных констант: в частности, NULCHAR ('\0'), SPACECHAR (''), TABCHAR ('), DQUOTECHAR ('"') и SLASHCHAR ('\').

Чтобы поддерживать символы как char, так и wchar_t, необходимо дважды реализовать функцию следующим образом.

inline size_t CommandLineToArgVector(
    const char* commandLine,
    std::vector<std::string>& arg_vector)
{
    arg_vector.clear();
    /* Code omitted. */
    return static_cast<size_t>(arg_vector.size());
}
inline size_t CommandLineToArgVector(
    const wchar_t* commandLine,
    std::vector<std::wstring>& arg_vector)
{
    arg_vector.clear();
    /* Code omitted. */
    return static_cast<size_t>(arg_vector.size());
}

Очевидное решение состоит в том, чтобы реализовать это как функцию шаблона, которая принимает тип символа в качестве параметра шаблона следующим образом.

template<typename CharType>
size_t CommandLineToArgVector(
    const CharType* commandLine,
    std::vector< std::basic_string<CharType> >& arg_vector)
{
    arg_vector.clear();
    /* Code omitted. */
    return static_cast<size_t>(arg_vector.size());
}

Единственное, что нам мешает это сделать, так это существование символьных констант. Как вы представляете константы, чтобы они могли быть любого типа данных?

Возможные решения

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

Расширить

Одним из возможных решений является использование std::ctype::widen следующим образом.

template <typename CharType>
CharType widenChar(
    const char ch, const std::locale& loc = std::locale())
{
    const auto& cType = std::use_facet<std::ctype<CharType>>(loc);
    return cType.widen(ch);
}

Этот же метод можно расширить для использования со строковыми литералами следующим образом.

template<typename CharType>
std::basic_string<CharType> widenString(
    const char* str, const std::locale& loc = std::locale())
{
    std::basic_string<CharType> ret;
    if (str == nullptr || str[0] == 0)
    {
        return ret;
    }
    const auto& cType = std::use_facet<std::ctype<CharType>>(loc);
    auto srcLen = std::strlen(str);
    auto bufferSize = srcLen + 32;
    auto tmpPtr = yekneb::make_unique<CharType[]>(bufferSize);
    auto tmp = tmpPtr.get();
    cType.widen(str, str + srcLen, tmp);
    ret = tmp;
    return ret;
}

Затем в функции шаблона вы можете использовать символьные константы в нейтральном типе символов следующим образом.

const CharType NULCHAR = widenChar<CharType>('\0');
const CharType SPACECHAR = widenChar<CharType>(' ');
const CharType TABCHAR =  widenChar<CharType>('\t');
const CharType DQUOTECHAR = widenChar<CharType>('\"');
const CharType SLASHCHAR =  widenChar<CharType>('\\');

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

Вопрос в том, как решить эту проблему без этих проблем с производительностью.

Характеристики алгоритма

Другой вариант — создать класс признаков алгоритма, который включает функции, возвращающие каждую из констант, как показано ниже.

template <typename CharType>
struct CommandLineToArgVector_Traits
{
    CharType NULCHAR();
    CharType SPACECHAR();
    CharType TABCHAR();
    CharType DQUOTECHAR();
    CharType SLASHCHAR();
    CharType* STRING();
};
template<>
struct CommandLineToArgVector_Traits<char>
{
    char NULCHAR()
    {
        return '\0';
    }
    char SPACECHAR()
    {
        return ' ';
    }
    char TABCHAR()
    {
        return '\t';
    }
    char DQUOTECHAR()
    {
        return '\"';
    }
    char SLASHCHAR()
    {
        return '\\';
    }
    char* STRING()
    {
        return "String";
    }
};
template<>
struct CommandLineToArgVector_Traits<wchar_t>
{
    wchar_t NULCHAR()
    {
        return L'\0';
    }
    wchar_t SPACECHAR()
    {
        return L' ';
    }
    wchar_t TABCHAR()
    {
        return L'\t';
    }
    wchar_t DQUOTECHAR()
    {
        return L'\"';
    }
    wchar_t SLASHCHAR()
    {
        return L'\\';
    }
    wchar_t* STRING()
    {
        return L"String";
    }
};

Как видите, включить строковые константы в структуру трейтов алгоритма несложно.

Затем структуру свойств алгоритма можно использовать в функции шаблона следующим образом.

CommandLineToArgVector_Traits<CharType> traits;
const CharType NULCHAR = traits.NULCHAR();
const CharType SPACECHAR = traits.SPACECHAR();
const CharType TABCHAR =  traits.TABCHAR();
const CharType DQUOTECHAR = traits.DQUOTECHAR();
const CharType SLASHCHAR =  traits.SLASHCHAR();
const CharType* STRING = traits.STRING();

У этого решения нет таких проблем с производительностью, как у решения widenChar, но этот прирост производительности достигается за счет значительного увеличения сложности кода. Кроме того, теперь необходимо поддерживать две реализации структуры Algorithm Traits, а это означает, что мы вернулись к тому, с чего начали, хотя, по общему признанию, поддержка двух реализаций структуры Algorithm Traits требует гораздо меньше усилий, чем поддержка двух реализаций структуры Algorithm Traits. алгоритм.

Было бы идеально разработать решение, обладающее удобством и простотой решения widenChar без проблем с производительностью. Вопрос в том, как.

Магия препроцессора и шаблонов

К счастью, эту проблему можно решить, используя препроцессор и магию шаблонов. Я нашел это решение в статье Stack Overflow Как выразить строковый литерал в шаблоне, параметризованном типом символов, используемых для представления литерала.

Решение заключается в следующем.

template<typename CharType>
CharType CharConstantOfType(
    const char c, const wchar_t w);
template<>
char CharConstantOfType<char>(
    const char c, const wchar_t /*w*/)
{
    return c;
}
template<>
wchar_t CharConstantOfType<wchar_t>(
    const char /*c*/, const wchar_t w)
{
    return w;
}
template<typename CharType>
const CharType* StringConstantOfType(
    const char* c, const wchar_t* w);
template<>
const char* StringConstantOfType<char>(
    const char* c, const wchar_t* /*w*/)
{
    return c;
}
template<>
const wchar_t* StringConstantOfType<wchar_t>(
    const char* /*c*/, const wchar_t* w)
{
    return w;
}
#define _TOWSTRING(x) L##x
#define TOWSTRING(x) _TOWSTRING(x)
#define CHAR_CONSTANT(TYPE, STRING) \
  CharConstantOfType<TYPE>(STRING, TOWSTRING(STRING))
#define STRING_CONSTANT(TYPE, STRING) \
  StringConstantOfType<TYPE>(STRING, TOWSTRING(STRING))

Затем в функции шаблона вы можете использовать символьные константы в нейтральном типе символов следующим образом.

const CharType NULCHAR = CHAR_CONSTANT(CharType, '\0');
const CharType SPACECHAR = CHAR_CONSTANT(CharType, ' ');
const CharType TABCHAR =  CHAR_CONSTANT(CharType, '\t');
const CharType DQUOTECHAR = CHAR_CONSTANT(CharType, '\"');
const CharType SLASHCHAR =  CHAR_CONSTANT(CharType, '\\');
const CharType* STRING = STRING_CONSTANT(CharType, "String");

Абракадабра. Проблема решается без дублирования кода или проблем с производительностью.

Исходный код статьи

Исходный код этой статьи можно найти на сайте SullivanAndKey.com. Соответствующие файлы можно найти в следующих местах.

  • widen.h: содержит исходный код widenChar и widenString.
  • ConstantOfType.h: содержит источник CharConstantOfType и StringConstantOfType.
  • CmdLineToArgv.h: файл заголовка для функции CommandLineToArgVector.
  • CmdLineToArgv.cpp: исходный код функции CommandLineToArgVector.

Первоначально опубликовано на yekneb.com 11 ноября 2018 г.