有多种方法可以实现此目的,具体取决于 C++ 标准的可用版本。
如果您有可用的 C++17,您可以向下滚动到Method 3,我认为这是最优雅的解决方案。
Note:方法 1 和 3 假设字符串文字的字符将被限制为7 位 ASCII。这要求字符在 [0..127] 范围内并且执行字符集 https://en.cppreference.com/w/cpp/language/translation_phases与 7 位 ASCII 兼容(例如Windows-1252 https://en.wikipedia.org/wiki/Windows-1252 or UTF-8 https://en.wikipedia.org/wiki/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)
(另一种变体显示在这个答案 https://stackoverflow.com/a/52742402/7571258.)
对于长字符串,聚合初始化方法可能很麻烦。为了更舒适,我们可以使用模板专门化和宏的组合:
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 ) )
Usage:
template<typename CharType>
class StringTraits {
public:
static constexpr CharType const* WHITESPACE_STR = NARROW_OR_WIDE( CharType, " " );
};
Coliru 现场演示 http://coliru.stacked-crooked.com/a/696522212d9905ee
解释:
模板函数NarrowOrWide()
返回第一个 (char const*
)或第二个(wchar_t const*
) 参数,取决于模板参数CharT
.
宏观NARROW_OR_WIDE
用于避免必须同时写入窄字符串和宽字符串文字。宏观TOWSTRING
只是在前面加上L
给定字符串文字的前缀。
当然,只有当字符范围仅限于基本 ASCII 时,该宏才会起作用,但这通常就足够了。否则可以使用NarrowOrWide()
分别定义窄字符串和宽字符串文字的模板函数。
Notes:
我会在宏名称中添加一个“唯一”前缀,例如库的名称,以避免与其他地方定义的类似宏发生冲突。
方法 3 - 通过模板参数包初始化数组 (C++17)
C++17终于允许我们摆脱宏并使用纯C++解决方案。该解决方案使用模板参数包 https://en.cppreference.com/w/cpp/language/parameter_pack扩展以从字符串文字初始化数组,同时static_cast
将各个字符转换为所需的类型。
首先我们声明一个str_array
类,类似于std::array https://en.cppreference.com/w/cpp/container/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
来自字符串文字 whilestatic_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{} );
}
The 模板参数包 https://en.cppreference.com/w/cpp/language/parameter_pack需要扩展技巧,因为常量数组只能通过聚合初始化来初始化(例如const str_array<char,3> = {'a','b','c',0};
),所以我们必须将字符串文字“转换”为这样的初始值设定项列表。
如果任何字符超出基本 ASCII 范围 (0..127),则代码会触发编译时错误,原因在本答案开头给出。有些代码页 0..127 未映射到 ASCII,因此此检查并不能提供 100% 的安全性。
Usage:
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 现场演示 http://coliru.stacked-crooked.com/a/bda5fa86db31616a