Есть несколько способов сделать это, в зависимости от доступной версии стандарта 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