Compiling a string at compile time

I have a string literal with a value that is out of my control (for example, #define in the config.h file) and I want to initialize with it a global array of characters of a fixed size. If the string is too long, I want it to be truncated.

Basically, I want to achieve an effect

 #define SOMETEXT "lorem ipsum" #define LIMIT 8 char text[LIMIT + 1]; std::strncpy(text, SOMETEXT, LIMIT); text[LIMIT] = '\0'; 

except that I cannot use this code because I want text be statically initialized constexpr .

How can i do this?

Note. I already found a solution to this problem, but since the search in Qaru did not give me a satisfactory result (although there are many useful hints for such problems), I wanted to share my solution. If you have a better (more elegant) solution, please show it. I will accept the most elegant answer in one week.

+1
source share
2 answers

Alternative to creating std::array :

 namespace detail { template <typename C, std::size_t N, std::size_t...Is> constexpr std::array<C, sizeof...(Is) + 1> truncate(const C(&s)[N], std::index_sequence<Is...>) { return {(Is < N ? s[Is] : static_cast<C>(0))..., static_cast<C>(0)}; } } template <std::size_t L, typename C, std::size_t N> constexpr std::array<C, L + 1> truncate(const C(&s)[N]) { return detail::truncate(s, std::make_index_sequence<L>{}); } 

Demo

+2
source

The first step in solving this problem is its formalization. Given a string (sequence of characters)

s = s 0 , & hellip ;, s m sub>

with s i = 0 if and only if i = m for i = 0, ..., m and m โˆˆ โ„• and a number n โˆˆ โ„•, we want to obtain another string (character sequence)

t = t 0 , & hellip ;, t n sub>

from

  • t i = 0 if i = n ,
  • t i = s i if i m and
  • t i = 0 otherwise

for i = 0, & hellip ;, n .

Next, understand that the length of the string ( m in the above formalization) is easily calculated at compile time:

 template <typename CharT> constexpr auto strlen_c(const CharT *const string) noexcept { auto count = static_cast<std::size_t>(0); for (auto s = string; *s; ++s) ++count; return count; } 

I use C ++ 14 functions such as return type inference and generic constexpr functions.

Now the function defined by i & isin; 0, & hellip ;, n , calculates t i also in a straightforward manner.

 template <typename CharT> constexpr auto char_at(const CharT *const string, const std::size_t i) noexcept { return (strlen_c(string) > i) ? string[i] : static_cast<CharT>(0); } 

If we know n ahead of time, we can use this to put together the first quick and dirty solution:

 constexpr char text[] = { char_at(SOMETEXT, 0), char_at(SOMETEXT, 1), char_at(SOMETEXT, 2), char_at(SOMETEXT, 3), char_at(SOMETEXT, 4), char_at(SOMETEXT, 5), char_at(SOMETEXT, 6), char_at(SOMETEXT, 7), '\0' }; 

It compiles and initializes text desired values, but that applies to all the good that can be said about it. The fact that the string length is uselessly computed over and over with every char_at call is probably the least problem. More problematic is the fact that the solution (as ugly as it already is) clearly becomes completely cumbersome if n approaches larger values โ€‹โ€‹and that the constant n is implicitly hard-coded. Don't even consider using tricks such as

 constexpr char text[LIMIT] = { #if LIMIT > 0 char_at(SOMETEXT, 0), #endif #if LIMIT > 1 char_at(SOMETEXT, 1), #endif #if LIMIT > 2 char_at(SOMETEXT, 2), #endif // ... #if LIMIT > N # error "LIMIT > N" #endif '\0' }; 

to get around this limitation. The Boost.Preprocessor library may help clear up this mess a bit, but it's not worth it. There is a much cleaner solution using metaprogramming of patterns waiting around the corner.

Let's see how we can write a function that returns a correctly initialized array at compile time. Since the function cannot return an array, we need to wrap it in a struct , but as it turned out, std::array already does this (and more) for us, so we will use it.

I define the struct template helper using the static help function, which returns the desired std::array . In addition to the CharT character type parameter, this struct shaded by the length N by which the string is truncated (equivalent to n in the above formalization) and the number M characters that we have already added (this has nothing to do with the variable m in the above formalization).

 template <std::size_t N, std::size_t M, typename CharT> struct truncation_helper { template <typename... CharTs> static constexpr auto help(const CharT *const string, const std::size_t length, const CharTs... chars) noexcept { static_assert(sizeof...(chars) == M, "wrong instantiation"); const auto c = (length > M) ? string[M] : static_cast<CharT>(0); return truncation_helper<N, M + 1, CharT>::help(string, length, chars..., c); } }; 

As you can see, truncation_helper::help calls itself recursively, picking one character from the front of the line, which is truncated when it goes. I pass the length of the string around as an optional parameter to avoid recalculation in every recursive call.

We complete the process when M reaches N , providing this partial specialization. This is also the reason that I need a struct because function templates cannot be partially specialized.

 template <std::size_t N, typename CharT> struct truncation_helper<N, N, CharT> { template <typename... CharTs> static constexpr auto help(const CharT *, // ignored const std::size_t, // ignored const CharTs... chars) noexcept { static_assert(sizeof...(chars) == N, "wrong instantiation"); return truncation_helper::workaround(chars..., static_cast<CharT>(0)); } template <typename... CharTs> static constexpr auto workaround(const CharTs... chars) noexcept { static_assert(sizeof...(chars) == N + 1, "wrong instantiation"); std::array<CharT, N + 1> result = { chars... }; return result; } }; 

The final call to help does not use the string and length parameters, but must accept them, however, for compatibility.

For reasons that I donโ€™t understand, I canโ€™t use

 std::array<CharT, N + 1> result = { chars..., 0 }; return result; 

but instead, you need to call the helper-helper function workaround .

What smells a bit like this solution is that I need static_assert ions to make sure that the correct instance creation is invoked and that my solution introduces all those parameters of type CharTs... when we already know that the type should be CharT for everyone chars... parameters chars...

Combining all this, we get the following solution.

 #include <array> #include <cstddef> namespace my { namespace detail { template <typename CharT> constexpr auto strlen_c(const CharT *const string) noexcept { auto count = static_cast<std::size_t>(0); for (auto s = string; *s; ++s) ++count; return count; } template <std::size_t N, std::size_t M, typename CharT> struct truncation_helper { template <typename... CharTs> static constexpr auto help(const CharT *const string, const std::size_t length, const CharTs... chars) noexcept { static_assert(sizeof...(chars) == M, "wrong instantiation"); const auto c = (length > M) ? string[M] : static_cast<CharT>(0); return truncation_helper<N, M + 1, CharT>::help(string, length, chars..., c); } }; template <std::size_t N, typename CharT> struct truncation_helper<N, N, CharT> { template <typename... CharTs> static constexpr auto help(const CharT *, const std::size_t, const CharTs... chars) noexcept { static_assert(sizeof...(chars) == N, "wrong instantiation"); return truncation_helper::workaround(chars..., static_cast<CharT>(0)); } template <typename... CharTs> static constexpr auto workaround(const CharTs... chars) noexcept { static_assert(sizeof...(chars) == N + 1, "wrong instantiation"); std::array<CharT, N + 1> result = { chars... }; return result; } }; } // namespace detail template <std::size_t N, typename CharT> constexpr auto truncate(const CharT *const string) noexcept { const auto length = detail::strlen_c(string); return detail::truncation_helper<N, 0, CharT>::help(string, length); } } // namespace my 

Then it can be used as follows:

 #include <cstdio> #include <cstring> #include "my_truncate.hxx" // suppose we've put above code in this file #ifndef SOMETEXT # define SOMETEXT "example" #endif namespace /* anonymous */ { constexpr auto limit = static_cast<std::size_t>(8); constexpr auto text = my::truncate<limit>(SOMETEXT); } int main() { std::printf("text = \"%s\"\n", text.data()); std::printf("len(text) = %lu <= %lu\n", std::strlen(text.data()), limit); } 

Acknowledgments . This solution was triggered by the following answer: C ++ 11: create a constexpr array in C ++ from 0 to N

+2
source

All Articles