Как определить строковый литерал с типом символа, который зависит от параметра шаблона? - PullRequest
0 голосов
/ 10 октября 2018
template<typename CharType>
class StringTraits {
public:
    static const CharType NULL_CHAR = '\0';
    static constexpr CharType* WHITESPACE_STR = " ";
};

typedef StringTraits<char> AStringTraits;
typedef StringTraits<wchar_t> WStringTraits;

Я знаю, что мог бы сделать это со специализацией шаблона, но это потребовало бы некоторого дублирования (путем определения строковых литералов с префиксом L и без него).

Существует ли более простой способ определения const/ constexpr char / wchar_t и char * / wchar_t * с одинаковым строковым литералом в классе шаблона?

Ответы [ 2 ]

0 голосов
/ 10 октября 2018

Вот альтернативная реализация, основанная на ответе @ zett42.Пожалуйста, сообщите мне.

#include <iostream>
#include <tuple>

#define TOWSTRING_(x) L##x
#define TOWSTRING(x) TOWSTRING_(x)  
#define MAKE_LPCTSTR(C, STR) (std::get<const C*>(std::tuple<const char*, const wchar_t*>(STR, TOWSTRING(STR))))

template<typename CharType>
class StringTraits {
public:
    static constexpr const CharType* WHITESPACE_STR = MAKE_LPCTSTR(CharType, "abc");
};

typedef StringTraits<char> AStringTraits;
typedef StringTraits<wchar_t> WStringTraits;

int main(int argc, char** argv) {
    std::cout << "Narrow string literal: " << AStringTraits::WHITESPACE_STR << std::endl;
    std::wcout << "Wide string literal  : " << WStringTraits::WHITESPACE_STR << std::endl;
    return 0;
}
0 голосов
/ 10 октября 2018

Есть несколько способов сделать это, в зависимости от доступной версии стандарта C ++.Если у вас есть C ++ 17, вы можете прокрутить вниз до Метод 3 , что, по моему мнению, является наиболее элегантным решением.

Примечание: Методы 1 и3 предположим, что символы строкового литерала будут ограничены 7-битным ASCII .Для этого необходимо, чтобы символы находились в диапазоне [0..127], а набор исполняемых символов был совместим с 7-битным ASCII (например, Windows-1252 или UTF-8).В противном случае простое приведение значений char к wchar_t, используемое этими методами, не даст правильного результата.

Метод 1 - совокупная инициализация (C ++ 03)

НаиболееСамый простой способ - определить массив с помощью агрегатной инициализации:

template<typename CharType>
class StringTraits {
public:
    static const CharType NULL_CHAR = '\0';
    static constexpr CharType[] WHITESPACE_STR = {'a','b','c',0};
};

Метод 2 - специализация шаблона и макрос (C ++ 03)

(Другой вариант показан в этот ответ .)

Метод агрегированной инициализации может быть громоздким для длинных строк.Для большего удобства мы можем использовать комбинацию специализации шаблонов и макросов:

template< typename CharT > constexpr CharT const* NarrowOrWide( char const*, wchar_t const* );
template<> constexpr char const* NarrowOrWide< char >( char const* c, wchar_t const* )       
    { return c; }
template<> constexpr wchar_t const* NarrowOrWide< wchar_t >( char const*, wchar_t const* w ) 
    { return w; }

#define TOWSTRING1(x) L##x
#define TOWSTRING(x) TOWSTRING1(x)  
#define NARROW_OR_WIDE( C, STR ) NarrowOrWide< C >( ( STR ), TOWSTRING( STR ) )

Использование:

template<typename CharType>
class StringTraits {
public:
    static constexpr CharType const* WHITESPACE_STR = NARROW_OR_WIDE( CharType, " " );
};

Live Demo atColiru

Объяснение:

Функция шаблона NarrowOrWide() возвращает либо первое (char const*), либо второе (wchar_t const*)аргумент, в зависимости от параметра шаблона CharT.

Макрос NARROW_OR_WIDE используется, чтобы избежать необходимости записывать как узкий, так и широкий строковый литерал.Макрос TOWSTRING просто добавляет префикс L к данному строковому литералу.

Конечно, макрос будет работать только в том случае, если диапазон символов ограничен базовым ASCII, но этого обычно достаточно.В противном случае можно использовать шаблонную функцию NarrowOrWide(), чтобы определять узкие и широкие строковые литералы отдельно.

Примечания:

Я бы добавил «уникальный» префикс в макросимена, что-то вроде имени вашей библиотеки, чтобы избежать конфликтов с похожими макросами, определенными в других местах.


Метод 3 - массив, инициализированный с помощью пакета параметров шаблона (C ++ 17)

CНаконец, ++ 17 позволяет нам избавиться от макроса и использовать чистое решение C ++.В решении используется пакет параметров шаблона расширение для инициализации массива из строкового литерала при static_cast переводе отдельных символов в требуемый тип.

Сначала мы объявляем класс str_array, которыйаналогично std::array, но с учетом постоянной строки с нулевым символом в конце (например, str_array::size() возвращает количество символов без '\0' вместо размера буфера).Этот класс-обертка необходим, потому что простой массив не может быть возвращен из функции.Он должен быть заключен в структуру или класс.

template< typename CharT, std::size_t Length >
struct str_array
{
    constexpr CharT const* c_str()              const { return data_; }
    constexpr CharT const* data()               const { return data_; }
    constexpr CharT operator[]( std::size_t i ) const { return data_[ i ]; }
    constexpr CharT const* begin()              const { return data_; }
    constexpr CharT const* end()                const { return data_ + Length; }
    constexpr std::size_t size()                const { return Length; }
    // TODO: add more members of std::basic_string

    CharT data_[ Length + 1 ];  // +1 for null-terminator
};

Пока ничего особенного.Настоящий трюк выполняется с помощью следующей функции str_array_cast(), которая инициализирует str_array из строкового литерала, а static_cast переводит отдельные символы в нужный тип:

#include <utility>

namespace detail {
    template< typename ResT, typename SrcT >
    constexpr ResT static_cast_ascii( SrcT x )
    {
        if( !( x >= 0 && x <= 127 ) )
            throw std::out_of_range( "Character value must be in basic ASCII range (0..127)" );
        return static_cast<ResT>( x );
    }

    template< typename ResElemT, typename SrcElemT, std::size_t N, std::size_t... I >
    constexpr str_array< ResElemT, N - 1 > do_str_array_cast( const SrcElemT(&a)[N], std::index_sequence<I...> )
    {
        return { static_cast_ascii<ResElemT>( a[I] )..., 0 };
    }
} //namespace detail

template< typename ResElemT, typename SrcElemT, std::size_t N, typename Indices = std::make_index_sequence< N - 1 > >
constexpr str_array< ResElemT, N - 1 > str_array_cast( const SrcElemT(&a)[N] )
{
    return detail::do_str_array_cast< ResElemT >( a, Indices{} );
}

Шаблон Пакет параметров Требуется расширение для расширения, поскольку константные массивы могут быть инициализированы только через агрегатную инициализацию (например, const str_array<char,3> = {'a','b','c',0};), поэтому мы должны «преобразовать» строковый литерал в такой список инициализаторов.

Код вызывает ошибку времени компиляции, если какой-либо символ находится за пределами базового диапазона ASCII (0..127), по причинам, указанным в начале этого ответа.Существуют кодовые страницы, где 0..127 не сопоставляется с ASCII, поэтому эта проверка не дает 100% безопасности.

Использование:

template< typename CharT >
struct StringTraits
{
    static constexpr auto WHITESPACE_STR = str_array_cast<CharT>( "abc" );

    // Fails to compile (as intended), because characters are not basic ASCII.
    //static constexpr auto WHITESPACE_STR1 = str_array_cast<CharT>( "äöü" );
};

Демоверсия в Coliru

...