Хранить ненулевую концевую строковую константу C в C++
Прежде чем кто-то скажет: "НЕ ДЕЛАЙТЕ ЭТОГО, так как это действительно плохо".
- Я понимаю причины наличия строки, заканчивающейся NUL.
- Я знаю, что можно сказать что-то вроде
char mystr[] = { 'm', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g'};
Однако удобство представления c-string слишком велико.
Рациональным для этого является то, что я программирую для микроконтроллера, и мне нужно хранить данные в памяти программы. Некоторые данные представлены в форме байтов, слов, слов и плаваний. Я хотел бы, чтобы данные включали строки без NUL.
Я пробовал шаблоны, которые принимают
Если бы я мог переназначить строку как что-то вроде шаблона boost::mpl::vector_c
Есть идеи?
РЕДАКТИРОВАТЬ:
Пример псевдокода (это немного надумано, так как реальный код намного больше, также я бы, наверное, не читал побайтно, как этот, и не использовал бы литерал для итерации до конца строки. Это было бы встроено в данные тоже где то.)
// 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
Я тоже могу поместить это в тип:
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
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.
Конечно, для этой работы с C++11 constexpr
функции должны быть преобразованы в рекурсивные тернарные версии.