$
我知道我可以使用模板专业化来做到这一点,但这需要一些重复(通过定义带有和不带有$ ./gash.sh -r fred.txt
r
fred.txt
$ ./gash.sh -r
./gash.sh: option requires an argument -- r
$ ./gash.sh -x
./gash.sh: illegal option -- x
$ ./gash.sh -dr fred.txt
d
r
fred.txt
前缀的字符串文字)。
在模板类中是否有更简单的方法来定义具有相同 string文字的const / constexpr char / wchar_t和char * / wchar_t *?
答案 0 :(得分:5)
有多种方法可以执行此操作,具体取决于C ++标准的可用版本。 如果您有可用的C ++ 17,则可以向下滚动到方法3 ,这是我认为最优雅的解决方案。
注意:方法1和3假定字符串文字的字符将被限制为 7位ASCII 。这要求字符在[0..127]范围内,并且execution character set与7位ASCII兼容(例如Windows-1252或UTF-8)。否则,将这些方法使用的char
值简单地转换为wchar_t
并不会得出正确的结果。
最简单的方法是使用聚合初始化来定义数组:
template<typename CharType>
class StringTraits {
public:
static const CharType NULL_CHAR = '\0';
static constexpr CharType[] WHITESPACE_STR = {'a','b','c',0};
};
(另一个变体显示在this answer中。)
对于长字符串,聚合初始化方法可能很麻烦。为了更加舒适,我们可以结合使用模板专门化和宏:
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()
模板函数分别定义窄和宽字符串文字。
注释:
我会在宏名称(例如您的库名称)之前添加一个“唯一”前缀,以避免与在其他地方定义的相似宏发生冲突。
C ++ 17最终使我们摆脱了宏,并使用了纯C ++解决方案。该解决方案使用template parameter pack扩展从字符串文字初始化数组,同时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::runtime_error( "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{} );
}
需要template parameter pack扩展技巧,因为常量数组只能通过聚合初始化来初始化(例如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>( "äöü" );
};
答案 1 :(得分:2)
这是基于@ 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;
}
答案 2 :(得分:1)
这里是对现在常见的基于模板的解决方案的改进
保留C字符串的array[len]
C ++类型,而不是将它们衰减为指针,这意味着您可以在结果上调用sizeof() >并获得字符串+ NUL的大小,而不是指针的大小,就像您在其中拥有原始字符串一样。
即使不同编码的字符串在代码单位中具有不同的长度也可以工作(如果字符串具有非ASCII文本,这实际上得到保证)。
不会引起任何运行时开销,也不会尝试/不需要在运行时进行编码转换。
信用:这种改进从Mark Ransom的原始模板构思和zett42的#2构思开始,并借鉴了Chris Kushnir's answer的构思,但修正了大小限制。
此代码执行char和wchar_t,但是将其扩展为char8_t + char16_t + char32_t
是微不足道的// generic utility for C++ pre-processor concatenation
// - avoids a pre-processor issue if x and y have macros inside
#define _CPP_CONCAT(x, y) x ## y
#define CPP_CONCAT(x, y) _CPP_CONCAT(x, y)
// now onto stringlit()
template<size_t SZ0, size_t SZ1>
constexpr
auto _stringlit(char c,
const char (&s0) [SZ0],
const wchar_t (&s1) [SZ1]) -> const char(&)[SZ0]
{
return s0;
}
template<size_t SZ0, size_t SZ1>
constexpr
auto _stringlit(wchar_t c,
const char (&s0) [SZ0],
const wchar_t (&s1) [SZ1]) -> const wchar_t(&)[SZ1]
{
return s1;
}
#define stringlit(code_unit, lit) \
_stringlit(code_unit (), lit, CPP_CONCAT(L, lit))
在这里,我们不是在使用C ++重载,而是在每个char编码中定义一个函数,每个函数具有不同的签名。每个函数都返回具有原始边界的原始数组类型。选择适当功能的选择器是所需编码中的单个字符(该字符的值并不重要)。我们不能在模板参数中使用类型本身来进行选择,因为那样会导致重载并且返回类型冲突。此代码也可以在没有constexpr
的情况下使用。注意,我们返回的是对数组的引用(在C ++中是可能的),而不是对数组的引用(在C ++中是不允许的)。此处使用尾随返回类型语法是可选的,但比其他方法更具可读性,例如const char (&stringlit(...params here...))[SZ0]
等等。
我使用Visual Studio 2019 16.7(aka _MSC_VER
1927 aka pdb ver 14.27)中的clang 9.0.8和MSVC ++进行了编译。我启用了c++2a/c++latest
,但我认为C ++ 14或17足以满足此代码的需要。
享受!
答案 3 :(得分:0)
我刚刚得出一个紧凑的答案,该答案与其他C ++ 17版本相似。同样,它依赖于实现定义的行为,尤其是环境字符类型。它支持将ASCII和ISO-8859-1转换为UTF-16 wchar_t,UTF-32 wchar_t,UTF-16 char16_t和UTF-32 char32_t。不支持UTF-8输入,但是更详细的转换代码是可行的。
template <typename Ch, size_t S>
constexpr auto any_string(const char (&literal)[S]) -> const array<Ch, S> {
array<Ch, S> r = {};
for (size_t i = 0; i < S; i++)
r[i] = literal[i];
return r;
}
完整示例如下:
$ cat any_string.cpp
#include <array>
#include <fstream>
using namespace std;
template <typename Ch, size_t S>
constexpr auto any_string(const char (&literal)[S]) -> const array<Ch, S> {
array<Ch, S> r = {};
for (size_t i = 0; i < S; i++)
r[i] = literal[i];
return r;
}
int main(void)
{
auto s = any_string<char>("Hello");
auto ws = any_string<wchar_t>(", ");
auto s16 = any_string<char16_t>("World");
auto s32 = any_string<char32_t>("!\n");
ofstream f("s.txt");
f << s.data();
f.close();
wofstream wf("ws.txt");
wf << ws.data();
wf.close();
basic_ofstream<char16_t> f16("s16.txt");
f16 << s16.data();
f16.close();
basic_ofstream<char32_t> f32("s32.txt");
f32 << s32.data();
f32.close();
return 0;
}
$ c++ -o any_string any_string.cpp -std=c++17
$ ./any_string
$ cat s.txt ws.txt s16.txt s32.txt
Hello, World!
答案 4 :(得分:0)
上述zett42方法2的一种变体。 具有支持所有char类型(对于可以表示为char []的文字)并保留适当的字符串文字数组类型的优点。
首先是模板功能:
template<typename CHAR_T>
constexpr
auto LiteralChar(
char A,
wchar_t W,
char8_t U8,
char16_t U16,
char32_t U32
) -> CHAR_T
{
if constexpr( std::is_same_v<CHAR_T, char> ) return A;
else if constexpr( std::is_same_v<CHAR_T, wchar_t> ) return W;
else if constexpr( std::is_same_v<CHAR_T, char8_t> ) return U8;
else if constexpr( std::is_same_v<CHAR_T, char16_t> ) return U16;
else if constexpr( std::is_same_v<CHAR_T, char32_t> ) return U32;
}
template<typename CHAR_T, size_t SIZE>
constexpr
auto LiteralStr(
const char (&A) [SIZE],
const wchar_t (&W) [SIZE],
const char8_t (&U8) [SIZE],
const char16_t (&U16)[SIZE],
const char32_t (&U32)[SIZE]
) -> const CHAR_T(&)[SIZE]
{
if constexpr( std::is_same_v<CHAR_T, char> ) return A;
else if constexpr( std::is_same_v<CHAR_T, wchar_t> ) return W;
else if constexpr( std::is_same_v<CHAR_T, char8_t> ) return U8;
else if constexpr( std::is_same_v<CHAR_T, char16_t> ) return U16;
else if constexpr( std::is_same_v<CHAR_T, char32_t> ) return U32;
}
然后宏:
#define CMK_LC(CHAR_T, LITERAL) \
LiteralChar<CHAR_T>( LITERAL, L ## LITERAL, u8 ## LITERAL, u ## LITERAL, U ## LITERAL )
#define CMK_LS(CHAR_T, LITERAL) \
LiteralStr<CHAR_T>( LITERAL, L ## LITERAL, u8 ## LITERAL, u ## LITERAL, U ## LITERAL )
然后使用:
template<typename CHAR_T>
class StringTraits {
public:
struct LC { // literal character
static constexpr CHAR_T Null = CMK_LC(CHAR_T, '\0');
static constexpr CHAR_T Space = CMK_LC(CHAR_T, ' ');
};
struct LS { // literal string
// can't seem to avoid having to specify the size
static constexpr CHAR_T Space [2] = CMK_LS(CHAR_T, " ");
static constexpr CHAR_T Ellipsis [4] = CMK_LS(CHAR_T, "...");
};
};
auto char_space { StringTraits<char>::LC::Space };
auto wchar_space { StringTraits<wchar_t>::LC::Space };
auto char_ellipsis { StringTraits<char>::LS::Ellipsis }; // note: const char*
auto wchar_ellipsis { StringTraits<wchar_t>::LS::Ellipsis }; // note: const wchar_t*
auto (& char_space_array) [4] { StringTraits<char>::LS::Ellipsis };
auto (&wchar_space_array) [4] { StringTraits<wchar_t>::LS::Ellipsis };
? syntax to get a local copy ?
诚然,保留字符串文字数组类型的语法有点麻烦,但并不过分。
同样,仅适用于所有字符类型表示形式中具有相同代码单元编号的文字。
如果希望LiteralStr支持所有类型的所有文字,则可能需要将指针作为参数传递并返回CHAR_T *而不是CHAR_T(&)[SIZE]。不要以为可以让LiteralChar支持多字节char。
[编辑]
将Louis Semprini SIZE支持应用于LiteralStr可获得:
template<typename CHAR_T,
size_t SIZE_A, size_t SIZE_W, size_t SIZE_U8, size_t SIZE_U16, size_t SIZE_U32,
size_t SIZE_R =
std::is_same_v<CHAR_T, char> ? SIZE_A :
std::is_same_v<CHAR_T, wchar_t> ? SIZE_W :
std::is_same_v<CHAR_T, char8_t> ? SIZE_U8 :
std::is_same_v<CHAR_T, char16_t> ? SIZE_U16 :
std::is_same_v<CHAR_T, char32_t> ? SIZE_U32 : 0
>
constexpr
auto LiteralStr(
const char (&A) [SIZE_A],
const wchar_t (&W) [SIZE_W],
const char8_t (&U8) [SIZE_U8],
const char16_t (&U16) [SIZE_U16],
const char32_t (&U32) [SIZE_U32]
) -> const CHAR_T(&)[SIZE_R]
{
if constexpr( std::is_same_v<CHAR_T, char> ) return A;
else if constexpr( std::is_same_v<CHAR_T, wchar_t> ) return W;
else if constexpr( std::is_same_v<CHAR_T, char8_t> ) return U8;
else if constexpr( std::is_same_v<CHAR_T, char16_t> ) return U16;
else if constexpr( std::is_same_v<CHAR_T, char32_t> ) return U32;
}
也可以使用更简单的语法来创建变量; 例如,在StringTraits :: LS中可以将其更改为constexpr auto& 所以
static constexpr CHAR_T Ellipsis [4] = CMK_LS(CHAR_T, "...");
成为
static constexpr auto & Ellipsis { CMK_LS(CHAR_T, "...") };
使用CMK_LS(char,“ literal”)时,文字中的任何无效char都将转换为'?' VS 2019之前,还不确定其他编译器会做什么。