模板代码和文本字符串

Template code and literal strings

本文关键字:文本 字符串 代码      更新时间:2023-10-16

我目前正在编写一些模板代码,其中模板参数是要使用的字符类型。这会导致引用文本字符串时出现问题。当然,我可以用我使用的字符串做一个结构,但我在想是否有可能做这样的东西:

template<typename chartype, char32_t... chars>
struct tr {
/* some magic here */
};

这样tr<char32_t,U"hello world">会导致U"hello world"tr<char16_t,U"Hello world">会导致u"hello world"tr<char,U"hello world">将导致"hello world"(UTF-8)。

魔术当然应该正确地将上面的代码0x10000转换为引导代码并遵循char16_t代码以及UTF-8的正确2,3和4字节代码 在编译时。

问题是:如何使用char32_t... chars模板参数定义给定字符类型的常量 C 样式字符串?您可以提取字符 但是如何根据模板代码中输入字符串的字符重建新字符串?

请注意,如果您愿意,预处理器可以正确定义带有合适前缀 u 或 U 的字符串,例如"hello world",但它无法访问字符串的各个字符来正确翻译它。

编辑:

但是,在新C++中,字符串作为模板参数绝对是可能的, 模板参数未声明为const char *或类似的东西:

template <char... txt>
struct foo { ... }

允许您将foo<"hello">编写为类型,并将字符串"hello"作为模板参数。问题是如何从这些字符构建字符串。

我的意思是在某些时候您希望结构包含要返回的字符串值:

template <char32_t... txt>
struct foo;
template <>
struct foo<> {
static const char16_t * txt() { return ""; }
};
template <char32_t a, char32_t... rest>
struct foo<a, rest...> {
static const char16_t * txt()
{
char16_t buf[100];
int k = 0;
if (a < 0x10000) buf[k++] = a;
else {
buf[k++] = 0xd800 | ((a - 0x10000) >> 10);
buf[k++] = 0xdc00 | ((a-0x10000) & 0x3ff);
}
// copy rest of string into buf + 2..99
u16strcpy(buf + k, foo<rest...>::txt());
return buf;
}
}

这个"解决方案"有几个明显的问题,一个问题是 buf 只有 100 个字符的空间,如果字符串更长,这将失败。 但主要问题是我希望这发生在编译时,这对我来说看起来非常像运行时代码,根本不是我想做的。

基本上我想要这样工作的东西:

foo<char, "hello">生成的实际上是字符串文本的内容"hello"u8"hello".

foo<char16_t, "hello">生成的内容实际上是字符串文字u"hello"foo<char32_t, "hello">生成的实际上是字符串文字U"hello"

问题在于编写模板代码来处理各种字符格式,然后涉及字符串文本。是的,您可以编写一个简单的结构:

template <typename ChT> struct bar;
template <>
struct bar<char32_t> {
static const char32_t * txta = U"AAAA";
static const char32_t * txtb = U"BBBB";
};

等等,bar<char16_t>txta = u"AAAA"等等。然后参考字符串 在您的模板化代码中按bar<T>::txta等。但是,我希望有一种方法可以直接在模板化代码中指定这些字符串,并且编译器会做正确的事情。换句话说,模板化字符串文本。

也许它应该作为语言的一个功能添加T<char32_t> string-literal与 U 字符串文字等相同 这样你就可以写

template <typename ChT>
struct foo {
static const ChT * txta = T<ChT> "AAAAA";
};

编译器会做正确的事情。

这似乎根本不合法,甚至以下内容也被拒绝(vs2017,标准设置为最新):

template<char const * ptr>
struct test
{};
void bar()
{
test<"testing"> t;
}

错误:无效的表达式作为"PTR"的模板参数,如果这不起作用,尝试在编译时转换它是一个非启动器。老实说,指向数据的指针不够恒定似乎并不奇怪。作为模板参数。

以下是一些使其在C++17中工作的工具(可能可移植到 C++11 和 C++14):

模板化类的static constexpr数据成员

您希望使用的输出文本需要一些"存储"。我建议为每个文字实例化一个唯一的类模板,例如,Literal<char, 'f', 'o', 'o', ''>. That class can hold the data as a静态 constexpr' 成员。

template<class C, C... cs>
struct Literal {
static_assert(sizeof...(cs) >= 1);
static constexpr C data[] = {cs...};// or use `std::array<C, sizeof...(cs)>`
};
template<class C, C... cs>
constexpr C Literal<C, cs...>::data[];

用户定义的字符串文本以简化语法

当然,您希望避免键入,例如Literal<char, 'f', 'o', 'o', ''>。实现此目的的一个有用工具是用户定义的字符串文本的以下重载。

template<class C, C... cs>
constexpr Literal<C, cs..., C('')> operator""_c() {// or use `auto`
return Literal<C, cs..., C('')>{};
}

请注意如何将输入文本作为非类型模板参数传递给该重载。这样,就可以"将值作为类型携带"。

用于重新编码的constexpr算法

到目前为止,您可以键入"foo"_c来获取具有与"foo"相同的static constexpr数据成员的Literal<char, 'f', 'o', 'o', ''>。接下来,您可以将该Literal<char, 'f', 'o', 'o', ''>传递给一个函数,该函数将const char16_t(&)[4]返回给相应Literal<char16_t, ..., ''>data。语法可以读取tr<char16_t>("foo"_c)

Literal<char, ...>转换为相应Literal<char16_t, ...>的代码可能基于如下所示constexpr算法。

template<
class OutChar, class InChar, InChar... cs,
std::size_t... input_indices, std::size_t... output_indices
>
constexpr auto& tr_impl(// called by `tr` with appropriate `index_sequence`s
Literal<InChar, cs...>,
std::index_sequence<input_indices...>,
std::index_sequence<output_indices...>
) {
constexpr std::size_t outsize = sizeof...(output_indices);
using Buffer = std::array<OutChar, outsize>;
constexpr Buffer buf = encode_as<OutChar, outsize>({cs...});
return Literal<OutChar, buf[output_indices]...>::data;
}
template<class OutChar, class InChar, InChar... cs>
constexpr auto& tr(Literal<InChar, cs...> literal) {
constexpr std::size_t outsize = count_as<OutChar>({cs...});
return tr_impl<OutChar>(
literal,
std::make_index_sequence<sizeof...(cs)>{},// input indices
std::make_index_sequence<outsize>{}// output indices
);
}

剩下的部分将是实现这些功能count_asencode_as

在最终使用中分配给constexpr auto&

最后,您可以分配给constexpr auto&,以验证类型和值是否等效于基于所需字符类型的纯字符串文本。

static_assert(std::size(U"    ") == 3);
static_assert(std::size(u"    ") == 5);
constexpr auto& test = tr<char16_t>(U"    "_c);
static_assert(std::is_same<decltype(test), const char16_t(&)[5]>{});
for(std::size_t i=0; i<std::size(u"    "); ++i) {
assert(test[i] == u"    "[i]);
std::cout << i << ": " << test[i] << std::endl;
}