在有人说之前,“不要这样做,因为它真的很糟糕。”
char mystr[] = { 'm', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g'};但是,c-string表示的便利性太大了。
这样做的理由是我正在为微控制器编程,我需要将数据存储到程序的内存中。一些数据是字节,单词,双字和浮点数的形式。我希望数据包含没有NUL连续的字符串。
我尝试过< size_t N,char * A> 和< size_t N,char(& A)[N]> 的模板作为参数,以遍历数组并将其内容存储到静态数组,但我似乎无法正确。我认为标准可能实际上不允许这种情况在一般情况下是可以理解的,但在特定情况下是不幸的(特别是,这个。;):()
如果我可以将字符串重新映射为类似 boost :: mpl :: vector_c< char,...> 的模板,那会更好,因为我有其他代码可以存储它正确,但从模板中取消引用一个数组以用作const模板参数似乎也是不允许的。
有什么想法吗?
修改
Psudocode示例(这有点人为,因为实际代码要大得多,我也不会像这样逐字节读取,也不会使用文字迭代到字符串的末尾。这将是嵌入数据以及某处。):
// this stores bytes in an array
template<typename X, typename T, T ...numbers>
struct x
{
static PROGMEM volatile const T data[];
};
template<typename X, typename T, T ...numbers>
PROGMEM volatile const T x<X, T, numbers...>::data[] = { numbers... };
void main()
{
// this will not work, but the idea is you have byte 0 as 1,
// byte 1 as 2 byte 2 as 3 byte 3 as 's', byte 4 as 'o'...
// byte 22 as 'g', byte 23 as 4, byte 24 as 5, byte 25 as 6.
typedef x<int, char, 1,2,3,"some embedded string",4,5,6> xx;
for(i=0; i<20; ++i)
Serial.print(pgm_read_byte_near(&xx::data[0] + 3));
}
另请注意,我没有使用C ++ 11,这是C ++ 0x,可能还有扩展名。
答案 0 :(得分:3)
魔术和诡计
如果您使用的是C ++ 11(我知道,但在我缺席的情况下,我认为代码生成是您最好的选择),感觉用户定义的文字应该能够处理这个问题。例如,用:
template <char... RAW>
inline constexpr std::array<char, sizeof...(RAW)> operator "" _fixed() {
return std::array<char, sizeof...(RAW)>{RAW...};
}
如果这样做会很好:
const std::array<char, 7> goodbye = goodbye_fixed;
...但遗憾的是它没有(文字需要是数字,大概是出于解析的原因)。使用"goodbye"_fixed
也不起作用,因为它需要operator "" _fixed(const char *s, int length)
重载并且编译时数组已再次衰减为指针。
最后我们来调用这个:
const auto goodbye = operator "" _FS <'g','o','o','d','b','y','e'>();
它并不比丑陋的第一版更好。还有其他想法吗?
自动生成丑陋
我认为你是对的,你不能轻易拦截字符串文字机制。 老实说,通常的方法是使用构建工具在单独的文件中为您生成丑陋的代码(例如,参见国际化库)。
例如,您输入
fixed_string hello = "hello";
或专用文件中的类似内容,构建系统生成标题
const std::array<char, 5> hello;
和一个cpp,下面是 的丑陋初始化。
错过了“看起来像字符串文字”的要求
像这样?我试过模板......
#include <array>
const std::array<char, 5> hello = { 'h', 'e', 'l', 'l', 'o' };
#include <cstdio>
int main()
{
return std::printf("%.*s\n", hello.size(), &hello.front());
}
如果你没有C ++ 11,Boost.Array会起作用,或者你可以自己动手。
请注意,这只是const char[5]
周围的类型包装,所以应该可以进入数据段(我已经确认它使用我的本地gcc进入.rodata
。)
答案 1 :(得分:2)
我实际上忘记了这个Q,我不知道是否可以找到当时正在使用的原始代码,但是我已经弄清楚了如何在不终止NUL字符的情况下存储字符串。
在c ++ 17中,我能够用不包含尾随零的字符串填充constexpr
std::array<char, n>
。
#include <array>
#include <cstdio>
constexpr size_t str_len(char const * x)
{
char const * begin = x;
while (*x) {
++x;
}
return x - begin;
}
constexpr auto var = "hello there";
template <size_t I, size_t Max>
constexpr auto fn()
{
// Although I did this recursively, this could have also been done iteratively.
if constexpr (I < Max) {
auto x = fn<I + 1, Max>();
x[I] = var[I];
return x;
}
else {
return std::array<char, Max>{};
}
}
int main()
{
auto x = fn<0, str_len(var)>();
printf("'%*.*s'\n", x.size(), x.size(), x.data());
return 0;
}
这将进行以下汇编:
.LC0:
.string "'%*.*s'\n"
main:
sub rsp, 24
mov edx, 11
mov esi, 11
movabs rax, 7526676540175443304 ; <<< hello there
mov QWORD PTR [rsp+5], rax
mov eax, 29285
lea rcx, [rsp+5]
mov edi, OFFSET FLAT:.LC0
mov WORD PTR [rsp+13], ax
xor eax, eax
mov BYTE PTR [rsp+15], 101
call printf
xor eax, eax
add rsp, 24
ret
是的,7526676540175443304
是“你好”,没有任何终止NUL字符。 ?
参见 Demo 。
将main()
中的第一行放入全局空间将导致该字符串位于全局.text段中。
.LC0:
.string "'%*.*s'\n"
main:
sub rsp, 8
mov ecx, OFFSET FLAT:x
mov edx, 11
xor eax, eax
mov esi, 11
mov edi, OFFSET FLAT:.LC0
call printf
xor eax, eax
add rsp, 8
ret
x: ; <<< hello there
.byte 104
.byte 101
.byte 108
.byte 108
.byte 111
.byte 32
.byte 116
.byte 104
.byte 101
.byte 114
.byte 101
我也可以将其放入类型中:
template <char x, typename...Ts>
struct X
{
};
constexpr int str_len(char const * x)
{
char const * begin = x;
while (*x) {
++x;
}
return x - begin;
}
constexpr auto var = "hello there";
template <int I>
constexpr auto fn()
{
if constexpr (I - 1 != 0)
return X<var[str_len(var) - I], decltype(fn<I - 1>())>{};
else
return X<var[str_len(var) - I], void>{};
}
int main()
{
decltype(nullptr)(fn<str_len(var)>());
return 0;
}
哪个给我输出:
<source>:28:5: error: cannot convert 'X<'h', X<'e', X<'l', X<'l', X<'o', X<' ', X<'t', X<'h', X<'e', X<'r', X<'e', void> > > > > > > > > > >' to 'decltype(nullptr)' (aka 'nullptr_t') without a conversion operator
decltype(nullptr)(fn<str_len(var)>());
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
现在,我可以更加随意地按摩它,使其进入上面要求的状态。要求是将字符串存储为不以NULL结尾的字符串,也要在c ++ 0x中执行此操作,但这不是,所以我不会将其标记为答案。但我想我会把它放在那里。
似乎gnu和clang也具有扩展名,可以将字符串放入模板类型:
template <char...Cs>
struct chars {};
template <typename T, T...Xs>
chars<Xs...> operator""_xxx() {
return {};
}
int main()
{
decltype(nullptr)("hello there"_xxx);
return 0;
}
哪个吐出来:
<source>:5:14: warning: string literal operator templates are a GNU extension [-Wgnu-string-literal-operator-template]
chars<Xs...> operator""_xxx() {
^
<source>:11:5: error: cannot convert 'chars<'h', 'e', 'l', 'l', 'o', ' ', 't', 'h', 'e', 'r', 'e'>' to 'decltype(nullptr)' (aka 'nullptr_t') without a conversion operator
decltype(nullptr)("hello there"_xxx);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
请注意,我现在想到的唯一将字符串放入模板参数的原因是将字符串作为constexpr
进行传输,这可能有一些有趣的原因,例如允许对所述constexpr
函数的返回类型基于传递的字符串。这有一些有趣的可能性。
附加说明:不可能将字符串直接传递给constexpr
函数并使其变型返回类型,因为作为参数,它不再是constexpr
,这有点烦人。 操作是在函数外部将其声明为constexpr
字符串并修饰返回类型的唯一方法constexpr
,然后从中引用该外部constexpr
变量。在函数中,如我的第二个示例所示。
结果表明,尽管您不能直接将某些内容作为constexpr
值传递,但是可以传递一个lambda来用作constexpr
函数。
#include <array>
#include <cstdio>
constexpr size_t str_len(char const * x)
{
char const * begin = x;
while (*x) {
++x;
}
return x - begin;
}
template <size_t I = 0, typename FN>
constexpr auto fn2(FN str) {
constexpr auto Max = str_len(str());
if constexpr (I < Max) {
auto x = fn2<I + 1>(str);
x[I] = str()[I];
return x;
}
else {
return std::array<char, Max>{};
}
}
auto x = fn2<>([]{ return "hello there"; });
int main()
{
printf("'%*.*s'\n", x.size(), x.size(), x.data());
return 0;
}
与我的第一个示例产生相同的asm输出。 Demo
坦率的说,它确实有效。
鉴于我已经弄清楚了如何传递constexpr
字符串,所以我现在可以创建一个非递归类型:
#include <utility>
constexpr std::size_t str_len(char const * x)
{
char const * begin = x;
while (*x) {
++x;
}
return x - begin;
}
template <char...> struct c{};
template <typename FN, std::size_t...Is>
constexpr auto string_to_type_impl(FN str, std::index_sequence<Is...>)
{
return c<str()[Is]...>{};
}
template <typename FN>
constexpr auto string_to_type(FN str)
{
constexpr auto Max = str_len(str());
return string_to_type_impl(str, std::make_index_sequence<Max>{});
}
int main()
{
std::nullptr_t(string_to_type([]{ return "hello there"; }));
return 0;
}
输出结果:
<source>:29:5: error: cannot convert 'c<'h', 'e', 'l', 'l', 'o', ' ', 't', 'h', 'e', 'r', 'e'>' to 'std::nullptr_t' (aka 'nullptr_t') without a conversion operator
std::nullptr_t(string_to_type([]{ return "hello there"; }));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
当然,对于这些与c ++ 11一起使用的功能,constexpr
函数必须转换为递归三元版本。