c++ - 如何使用取决于模板参数的字符类型定义字符串文字?
问题描述
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*在模板类中具有相同的字符串文字?
解决方案
有几种方法可以做到这一点,具体取决于可用的 C++ 标准版本。如果你有 C++17 可用,你可以向下滚动到Method 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, " " );
};
解释:
模板函数根据模板参数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()
初始化 ,同时将单个字符 ing 为所需的类型: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>( "äöü" );
};
推荐阅读
- ruby-on-rails - Activemerchat 贝宝集成出现错误缺少必需参数:ip?
- java - 如何使用杰克逊根据运行时条件将集合序列化为空列表
- java - 类中的方法如何在对象之间共享?
- flutter - Flutter 数据存储:本地存储 vs 云存储
- android - AndroidX 依赖项和 XML 导致崩溃
- java - 玩游戏 - 寻找附近的玩家自定义 UI
- kubernetes - kubefed init 说“正在等待联邦控制平面启动”,但它永远不会出现
- ruby-on-rails - 如何找到属于当前用户的工作并在 Rails 中显示?
- android - 我应该把自动数据库更新的代码放在哪里?
- node.js - 两个 POST/GET 请求都会在 Postman 中产生 404 错误。是我的 Express 路由问题吗?