constexpr to concatenate two or more char strings
With C++17 the solution becomes very simple (here's the live version):
#include <initializer_list>
// we cannot return a char array from a function, therefore we need a wrapper
template <unsigned N>
struct String {
char c[N];
};
template<unsigned ...Len>
constexpr auto cat(const char (&...strings)[Len]) {
constexpr unsigned N = (... + Len) - sizeof...(Len);
String<N + 1> result = {};
result.c[N] = '\0';
char* dst = result.c;
for (const char* src : {strings...}) {
for (; *src != '\0'; src++, dst++) {
*dst = *src;
}
}
return result;
}
// can be used to build other constexpr functions
template<unsigned L>
constexpr auto makeCopyright(const char (&author)[L]) {
return cat("\xC2\xA9 ", author);
}
constexpr char one[] = "The desert was the apotheosis of all deserts";
constexpr char two[] = "huge, standing to the sky";
constexpr auto three = cat(
cat(one, ", ", two).c, // can concatenate recursively
" ",
"for what looked like eternity in all directions."); // can use in-place literals
constexpr auto phrase = cat(
three.c, // can reuse existing cats
"\n",
makeCopyright("Stephen King").c);
#include <cstdio>
int main() {
puts(phrase.c);
return 0;
}
template<size_t S>
using size=std::integral_constant<size_t, S>;
template<class T, size_t N>
constexpr size<N> length( T const(&)[N] ) { return {}; }
template<class T, size_t N>
constexpr size<N> length( std::array<T, N> const& ) { return {}; }
template<class T>
using length_t = decltype(length(std::declval<T>()));
constexpr size_t sum_string_sizes() { return 0; }
template<class...Ts>
constexpr size_t sum_string_sizes( size_t i, Ts... ts ) {
return (i?i-1:0) + sum_sizes(ts...);
}
then
template
template<unsigned N1, unsigned N2, class... Us>
constexpr auto
concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs)
-> std::array<char, sum_string_sizes( N1, N2, length_t<Us>::value... )+1 >
{
return concat(a1, concat(a2, xs...));
}
which gets rid of the recursion-in-decltype
.
Here is a full example using the above approach:
template<size_t S>
using size=std::integral_constant<size_t, S>;
template<class T, size_t N>
constexpr size<N> length( T const(&)[N] ) { return {}; }
template<class T, size_t N>
constexpr size<N> length( std::array<T, N> const& ) { return {}; }
template<class T>
using length_t = decltype(length(std::declval<T>()));
constexpr size_t string_size() { return 0; }
template<class...Ts>
constexpr size_t string_size( size_t i, Ts... ts ) {
return (i?i-1:0) + string_size(ts...);
}
template<class...Ts>
using string_length=size< string_size( length_t<Ts>{}... )>;
template<class...Ts>
using combined_string = std::array<char, string_length<Ts...>{}+1>;
template<class Lhs, class Rhs, unsigned...I1, unsigned...I2>
constexpr const combined_string<Lhs,Rhs>
concat_impl( Lhs const& lhs, Rhs const& rhs, seq<I1...>, seq<I2...>)
{
// the '\0' adds to symmetry:
return {{ lhs[I1]..., rhs[I2]..., '\0' }};
}
template<class Lhs, class Rhs>
constexpr const combined_string<Lhs,Rhs>
concat(Lhs const& lhs, Rhs const& rhs)
{
return concat_impl(
lhs, rhs,
gen_seq<string_length<Lhs>{}>{},
gen_seq<string_length<Rhs>{}>{}
);
}
template<class T0, class T1, class... Ts>
constexpr const combined_string<T0, T1, Ts...>
concat(T0 const&t0, T1 const&t1, Ts const&...ts)
{
return concat(t0, concat(t1, ts...));
}
template<class T>
constexpr const combined_string<T>
concat(T const&t) {
return concat(t, "");
}
constexpr const combined_string<>
concat() {
return concat("");
}
live example