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 *,
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