Хранить ненулевую концевую строковую константу C в C++

Прежде чем кто-то скажет: "НЕ ДЕЛАЙТЕ ЭТОГО, так как это действительно плохо".

  1. Я понимаю причины наличия строки, заканчивающейся NUL.
  2. Я знаю, что можно сказать что-то вроде
    char mystr[] = { 'm', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g'};
    Однако удобство представления c-string слишком велико.

Рациональным для этого является то, что я программирую для микроконтроллера, и мне нужно хранить данные в памяти программы. Некоторые данные представлены в форме байтов, слов, слов и плаваний. Я хотел бы, чтобы данные включали строки без NUL.

Я пробовал шаблоны, которые принимают и в качестве параметров, чтобы обойти массив и сохранить его содержимое в статическом массиве, но я не могу кажется, понял это правильно. Я думаю, что стандарт может фактически запретить это, что понятно в общем случае, но неудачно в конкретных случаях (в частности, в этом;;):()

Если бы я мог переназначить строку как что-то вроде шаблона boost::mpl::vector_c, это было бы лучше, так как у меня есть другой код, который будет правильно хранить его, но разыменовывать массив из шаблона в использоваться в качестве параметра шаблона 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 и, возможно, расширение.

1 ответ

Третья попытка

магия и обман

Если вы использовали 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], так что должно быть нормально, чтобы перейти в сегмент данных (я подтвердил, что идет в .rodata с моим местным gcc).

Я действительно потерял этот 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

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

Обратите внимание, что единственная причина, по которой я теперь могу придумать, чтобы поместить строку в аргумент шаблона, - это передать строку как constexpr, что могло иметь некоторые интересные причины, например, разрешение морфинга возвращаемого типа указанного constexprфункция на основе переданной строки. У которого есть несколько интересных возможностей.

Дополнительное примечание: невозможно передать строку напрямую в constexpr функция и изменит тип возвращаемого значения, потому что в качестве параметра он больше не constexpr, что немного раздражает. Единственный способ манипулироватьconstexpr string и преобразовать тип возвращаемого значения - объявить его внешним по отношению к функции как constexpr а затем укажите этот внешний constexpr переменная внутри функции, как показано во втором примере.

Редактировать 2

Оказывается, хотя вы не можете напрямую передать что-то как constexpr значение, вы можете передать лямбда, которая будет работать как 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 функции должны быть преобразованы в рекурсивные тернарные версии.

Другие вопросы по тегам