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.