Store nonsingular constant of string C in C ++

Before anyone says, "DO NOT DO IT, how really bad it is."

  • I understand the reasons for having a string terminated by NUL.
  • I know you can say something like
      char mystr [] = {'m', 'y', '', 's', 't', 'r', 'i', 'n', 'g'}; 
    However, the convenience of c-string representation is too great.

The rational for this is that I program the microcontroller, and I need to store the data in the program memory. Some of the data is presented as bytes, words, words, and floats. I would like the data to include strings without NUL contiguously.

I tried templates that take <size_t N, char * A> and <size_t N, char (& A) [N]> as parameters for moving the array and storing its contents in a static array, but I cannot figure out what it is right. I think that the standard can actually prohibit this, which is understandable in the general case, but unfortunate in specific cases (in particular, this one.;) :()

If I could reassign the string as something like the boost :: mpl :: vector_c <char, ...> template, that would be better, since I have other code that will store this correctly, but dereferencing the array from the template , which will be used as the template parameter const, is also prohibited.

Any ideas?

EDIT:

Psudocode example (this is curiously far-fetched, since the real code is much larger, I would also probably not read byte by byte like this, and I would not use a literal to iterate to the end of the line. Embedded in the data, as well as somewhere. ):

// 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)); } 

Also note that I am not using C ++ 11, it is C ++ 0x and possibly an extension.

+7
source share
2 answers

Third attempt

magic and deceit

If you used C ++ 11 (I know, but in his absence, I think code generation is your best bet), it seems like a custom literal should be able to handle this. For example, with:

 template <char... RAW> inline constexpr std::array<char, sizeof...(RAW)> operator "" _fixed() { return std::array<char, sizeof...(RAW)>{RAW...}; } 

it would be nice if this worked:

 const std::array<char, 7> goodbye = goodbye_fixed; 

... but, unfortunately, this is not so (the literal value must be numeric, presumably for the analysis of the reasons). Using "goodbye"_fixed does not work either, since this requires an overload of operator "" _fixed(const char *s, int length) , and the compile-time array operator "" _fixed(const char *s, int length) to a pointer again.

In the end, we come to this:

 const auto goodbye = operator "" _FS <'g','o','o','d','b','y','e'>(); 

and it's no better than the ugly first version. Any other ideas?


Second attempt

automatically generate ugliness

I think you are right that you cannot easily intercept a string literal. Honestly, the usual approach would be to use a build tool to create the ugly code for you in a separate file (for example, internationalization libraries, for example).

For example, you enter

 fixed_string hello = "hello"; 

or something like that in the highlighted file, and the build system generates a header

 const std::array<char, 5> hello; 

and cpp with ugly initialization from above below.


Try first

missed the requirement "looks like a string literal"

I tried templates ...

like this?

 #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()); } 

If you don't have C ++ 11, Boost.Array will work, or you can collapse yourself. Note that this is just a shell of type const char[5] , so it should be ok to go to the data segment (I confirmed that it goes to .rodata with my local gcc).

+3
source

I actually lost this Q, and I don’t know if I can find the original code that I worked with then, but I figured out how to save the string without the terminating NUL character.

In c ++ 17, I managed to populate constexpr std::array<char, n> with a character string that does not contain a trailing zero.

 #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; } 

This will produce the following assembly:

 .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 

Yes, 7526676540175443304 hello without the terminating NUL character. πŸ˜‚ See Demo .

Putting the first line in main() in global space will cause the line to be located in the global .text segment.

 .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

I can also put this in a type:

 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; } 

Which gives me the conclusion:

 <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

Now I can do even more massage to bring it into the state that I requested above. It was required to store the string as not NULL terminated, but also do it in c ++ 0x, which does not happen, so I will not mark this as an answer. But I thought that I would do it.

Edit

It seems that gnu and clang also have an extension that allows you to put a string in a template type:

 template <char...Cs> struct chars {}; template <typename T, T...Xs> chars<Xs...> operator""_xxx() { return {}; } int main() { decltype(nullptr)("hello there"_xxx); return 0; } 

Which spits out:

 <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

Note that the only reason I can come up with now to insert a string into the template argument is to pass the string as constexpr , which may have some interesting reasons for this, for example, allow morphing the return type of the specified constexpr function to based on the passed string. Which has some interesting features.

Additional note: It is not possible to pass a string directly to the constexpr function and change its return type, since it is no longer constexpr as a parameter, which is a little annoying. The only way to manipulate the constexpr string and transform the return type is to declare it external to the function as constexpr , and then refer to this external constexpr variable from the function, as shown in the second example below.

Edit 2

It turns out that although you cannot pass anything directly as a constexpr value, you can pass a lambda that will work as a constexpr function.

 #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; } 

Which leads to the same asm output as my first example. Demo

I am sincerely surprised that it actually works.

Edit 3

Given that I figured out how to pass a string to constexpr , now I can create a non-recursive type:

 #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; } 

With the result:

 <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

Of course, for this to work with c ++ 11, constexpr functions must be converted to recursive ternary versions.

+2
source

Source: https://habr.com/ru/post/944714/


All Articles