7

在任何人说“不要这样做,因为它真的很糟糕”之前。

  1. 我了解使用 NUL 终止字符串的原因。
  2. 我知道有人可以说类似
    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 模板参数似乎也是不允许的。

有任何想法吗?

编辑:

伪代码示例(这有点做作,因为实际代码要大得多,我也不会像这样逐字节读取,也不会使用文字迭代到字符串的末尾。那将嵌入到数据以及某处。):

// 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,可能是一个扩展。

4

2 回答 2

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;

...但遗憾的是它没有(文字需要是数字,大概是出于解析的原因)。using"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],所以应该可以进入数据段(我已经确认它.rodata与我的本地 gcc 一起进入)。

于 2013-05-10T13:44:34.660 回答
2

我实际上忘记了这个 Q 并且我不知道我是否能找到我当时正在使用的原始代码,但我已经想出了如何在没有终止 NUL 字符的情况下存储一个字符串。

在 c++17 中,我能够constexpr std::array<char, n>用不包含尾随零的字符串填充 a 。

#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

Demo

我也可以把它变成一个类型:

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)>());
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Demo

现在我可以多按摩一下,让它进入我上面要求的状态。要求是将字符串存储为非 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);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Demo

请注意,我现在能想到将字符串放入模板参数的唯一原因是将字符串作为 a 传输constexpr,这可能有一些有趣的原因,例如允许constexpr基于字符串通过。这有一些有趣的可能性。

附加说明:不可能将字符串直接传递给constexpr函数并让它变形返回类型,因为作为参数,它不再constexpr是 ,这有点烦人。 操作字符串和变形返回类型的唯一方法是将其声明为函数外部,然后从函数内部引用该外部变量,如我的第二个示例所示。constexprconstexprconstexpr

编辑 2

事实证明,虽然你不能直接将某些东西作为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

坦率地说,我很惊讶它确实有效。

编辑 3

鉴于我已经弄清楚如何传递一个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.

Demo

当然,对于 c++11 的这些工作,constexpr必须将函数转换为递归三元版本。

于 2019-08-19T23:03:08.047 回答