Concatenate Compile-Time Strings in a Template at Compile Time

Concatenate compile-time strings in a template at compile time?

You could use something like this. Everything happens at compile time. Specialize base_typename_struct to define your primitive types.

template <const char* str, int len, char... suffix>
struct append {
static constexpr const char* value() {
return append<str, len-1, str[len-1], suffix...>::value();
}
};

template <const char* str, char... suffix>
struct append<str, 0, suffix...> {
static const char value_str[];
static constexpr const char* value() {
return value_str;
}
};

template <const char* str, char... suffix>
const char append<str, 0, suffix...>::value_str[] = { suffix..., 0 };

template <typename T>
struct base_typename_struct;

template <>
struct base_typename_struct<int> {
static constexpr const char name[] = "int";
};

template <typename T, char... suffix>
struct typename_struct {
typedef base_typename_struct<T> base;
static const char* name() {
return append<base::name, sizeof(base::name)-1, suffix...>::value();
}
};

template <typename T, char... suffix>
struct typename_struct<T*, suffix...>:
public typename_struct<T, '*', suffix...> {
};

int main() {
cout << typename_struct<int****>::name() << endl;
}

Concatenate string literals at compile time

static_assert() is specified to accept string literal. Any constexpr function you can write can return a constant, but not literal. So you only may use preprocessor to construct strings for static_assert. Sorry.

Generating simple format string at compile time

You have several typos (and a concat limited to 2 argument).

template<int...I> using is      = std::integer_sequence<int,I...>;
template<int N> using make_is = std::make_integer_sequence<int,N>;

constexpr auto size(const char*s) { int i = 0; while(*s!=0){++i;++s;} return i; }

template<const char*, typename, const char*, typename>
struct concat_impl;

template<const char* S1, int... I1, const char* S2, int... I2>
struct concat_impl<S1, is<I1...>, S2, is<I2...>> {
static constexpr const char value[]
{
S1[I1]..., S2[I2]..., 0
};
};

template<const char* S1, const char* S2, const char*... Ss>
struct concat
{
constexpr static const char* value = concat_impl<S1, make_is<size(S1)>, concat<S2, Ss...>::value, make_is<size(concat<S2, Ss...>::value)>>::value;
};

template<const char* S1, const char* S2>
struct concat<S1, S2>
{
constexpr static const char* value = concat_impl<S1, make_is<size(S1)>, S2, make_is<size(S2)>>::value;
};

struct base_format_string_struct {
static constexpr const char format_string[] = "{}";
};

struct format_string_parameter_struct {
static constexpr const char parameter_string[] = " {}=\"{}\"";
};

template <int arg_count, const char*... params>
struct format_string_struct:
public format_string_struct<arg_count - 1, format_string_parameter_struct::parameter_string, params...> {
};

template <int arg_count>
struct format_string_struct<arg_count>:
public format_string_struct<arg_count - 1, format_string_parameter_struct::parameter_string> {
};

template <const char*... params>
struct format_string_struct<0, params...> {
static const char* format_string() {
return concat<base_format_string_struct::format_string, params...>::value;
}
};

Demo

But I would probably go with constexpr functions:

#if 1 // missing some constexpr for std::array/std::copy

template <typename T, std::size_t N>
struct my_array
{
T data[N];
};

template <typename CIT, typename IT>
constexpr void copy(CIT begin, CIT end, IT dest)
{
for (CIT it = begin; it != end; ++it)
{
*dest = *it;
++dest;
}
}
#endif

template <std::size_t N>
constexpr my_array<char, 2 + 8 * N + 1> format_string_struct_impl()
{
my_array<char, 2 + 8 * N + 1> res{};
constexpr char init[] = "{}"; // size == 2
constexpr char extra[] = R"( {}="{}")"; // size == 8
copy(init, init + 2, res.data);
for (std::size_t i = 0; i != N; ++i) {
copy(extra, extra + 8, res.data + 2 + i * 8);
}
return res;
}

template <std::size_t N>
constexpr my_array<char, 2 + 8 * N + 1> format_string_struct()
{
// Ensure the computation is done compile time.
constexpr auto res = format_string_struct_impl<N>();
return res;
}

Demo

Is it possible to concatenate two strings of type `const char *` at compile time?

Here is a quick compile time string class:

template<std::size_t N>
struct ct_str
{
char state[N+1] = {0};
constexpr ct_str( char const(&arr)[N+1] )
{
for (std::size_t i = 0; i < N; ++i)
state[i] = arr[i];
}
constexpr char operator[](std::size_t i) const { return state[i]; }
constexpr char& operator[](std::size_t i) { return state[i]; }

constexpr explicit operator char const*() const { return state; }
constexpr char const* data() const { return state; }
constexpr std::size_t size() const { return N; }
constexpr char const* begin() const { return state; }
constexpr char const* end() const { return begin()+size(); }

constexpr ct_str() = default;
constexpr ct_str( ct_str const& ) = default;
constexpr ct_str& operator=( ct_str const& ) = default;

template<std::size_t M>
friend constexpr ct_str<N+M> operator+( ct_str lhs, ct_str<M> rhs )
{
ct_str<N+M> retval;
for (std::size_t i = 0; i < N; ++i)
retval[i] = lhs[i];
for (std::size_t i = 0; i < M; ++i)
retval[N+i] = rhs[i];
return retval;
}

friend constexpr bool operator==( ct_str lhs, ct_str rhs )
{
for (std::size_t i = 0; i < N; ++i)
if (lhs[i] != rhs[i]) return false;
return true;
}
friend constexpr bool operator!=( ct_str lhs, ct_str rhs )
{
for (std::size_t i = 0; i < N; ++i)
if (lhs[i] != rhs[i]) return true;
return false;
}
template<std::size_t M, std::enable_if_t< M!=N, bool > = true>
friend constexpr bool operator!=( ct_str lhs, ct_str<M> rhs ) { return true; }
template<std::size_t M, std::enable_if_t< M!=N, bool > = true>
friend bool operator==( ct_str, ct_str<M> ) { return false; }
};

template<std::size_t N>
ct_str( char const(&)[N] )->ct_str<N-1>;

you can use it like this:

template <class T>
constexpr auto get_arithmetic_size()
{
if constexpr (sizeof(T)==1)
return ct_str{"1"};
if constexpr (sizeof(T)==2)
return ct_str{"2"};
if constexpr (sizeof(T)==4)
return ct_str{"4"};
if constexpr (sizeof(T)==8)
return ct_str{"8"};
if constexpr (sizeof(T)==16)
return ct_str{"16"};
}

template <class T, std::enable_if_t<std::is_arithmetic<T>{}, bool> = true>
constexpr auto make_type_name()
{
if constexpr (std::is_signed<T>{})
return ct_str{"int"} + get_arithmetic_size<T>();
else
return ct_str{"uint"} + get_arithmetic_size<T>();
}

which leads to statements like:

static_assert(make_type_name<int>() == make_type_name<int32_t>());

passing.

Live example.

Now one annoying thing is that the length of the buffer is in the type system. You could add a length field, and make N be "buffer size", and modify ct_str to only copy up to length and leave the trailing bytes as 0. Then override common_type to return the max N of both sides.

That would permit you do pass ct_str{"uint"} and ct_str{"int"} in the same type of value and make the implementation code a bit less annoying.

template<std::size_t N>
struct ct_str
{
char state[N+1] = {0};

template<std::size_t M, std::enable_if_t< (M<=N+1), bool > = true>
constexpr ct_str( char const(&arr)[M] ):
ct_str( arr, std::make_index_sequence<M>{} )
{}
template<std::size_t M, std::enable_if_t< (M<N), bool > = true >
constexpr ct_str( ct_str<M> const& o ):
ct_str( o, std::make_index_sequence<M>{} )
{}
private:
template<std::size_t M, std::size_t...Is>
constexpr ct_str( char const(&arr)[M], std::index_sequence<Is...> ):
state{ arr[Is]... }
{}
template<std::size_t M, std::size_t...Is>
constexpr ct_str( ct_str<M> const& o, std::index_sequence<Is...> ):
state{ o[Is]... }
{}
public:
constexpr char operator[](std::size_t i) const { return state[i]; }
constexpr char& operator[](std::size_t i) { return state[i]; }

constexpr explicit operator char const*() const { return state; }
constexpr char const* data() const { return state; }
constexpr std::size_t size() const {
std::size_t retval = 0;
while(state[retval]) {
++retval;
}
return retval;
}
constexpr char const* begin() const { return state; }
constexpr char const* end() const { return begin()+size(); }

constexpr ct_str() = default;
constexpr ct_str( ct_str const& ) = default;
constexpr ct_str& operator=( ct_str const& ) = default;

template<std::size_t M>
friend constexpr ct_str<N+M> operator+( ct_str lhs, ct_str<M> rhs )
{
ct_str<N+M> retval;
for (std::size_t i = 0; i < lhs.size(); ++i)
retval[i] = lhs[i];
for (std::size_t i = 0; i < rhs.size(); ++i)
retval[lhs.size()+i] = rhs[i];
return retval;
}

template<std::size_t M>
friend constexpr bool operator==( ct_str lhs, ct_str<M> rhs )
{
if (lhs.size() != rhs.size()) return false;
for (std::size_t i = 0; i < lhs.size(); ++i)
if (lhs[i] != rhs[i]) return false;
return true;
}
template<std::size_t M>
friend constexpr bool operator!=( ct_str lhs, ct_str<M> rhs )
{
if (lhs.size() != rhs.size()) return true;
for (std::size_t i = 0; i < lhs.size(); ++i)
if (lhs[i] != rhs[i]) return true;
return false;
}
};

template<std::size_t N>
ct_str( char const(&)[N] )->ct_str<N-1>;

The function implementations now become:

template <class T>
constexpr ct_str<2> get_arithmetic_size()
{
switch (sizeof(T)) {
case 1: return "1";
case 2: return "2";
case 4: return "4";
case 8: return "8";
case 16: return "16";
}

}

template <class T, std::enable_if_t<std::is_arithmetic<T>{}, bool> = true>
constexpr auto make_type_name()
{
constexpr auto base = std::is_signed<T>{}?ct_str{"int"}:ct_str{"uint"};
return base + get_arithmetic_size<T>();
}

which is a lot more natural to write.

Live example.

Concatenate const char at compile time when one of them is a function parameter

Parameter value cannot be used for constexpr.

You might turn:

template<typename... Args>
constexpr void critical(const char fmt[], Args&&... args)

into

template <char... cs, typename... Args>
void critical(std::integer_sequence<char, cs...>, Args&&... args)
{
constexpr char fullFmt[] = {'{', '}', ' ', cs... , '\0'};

spdlog::critical(fullFmt, m_prefix, std::forward<Args>(args)...);
}

to allow to have constexpr char array.

ensure-that-char-pointers-always-point-to-the-same-string-literal shows way to create similar sequences from literal string.
In your case, it would be:

template <typename Char, Char... Cs>
constexpr auto operator"" _cs() -> std::integer_sequence<Char, Cs...> {
return {};
}

Usage is then something like:

log.critical("My format {}"_cs, value); // gcc extension
log.critical(MAKE_SEQUENCE("My format {}"), value);

Demo with gcc extension.

Demo with MACRO.

Create a constexpr C string from concatenation of a some string literal and an int template parameter

Using static const would allow to compute it only once (but at runtime):

template<int size>
class MyClass
{
void onError()
{
static const std::string = "Error in MyClass of size "
+ std::to_string(size)
+ ": Detailed error description\n";

outputErrorMessage(errMsg);
}
};

If you really want to have that string at compile time, you might use std::array, something like:

template <std::size_t N>
constexpr std::size_t count_digit() {
if (N == 0) {
return 1;
}
std::size_t res = 0;
for (int i = N; i; i /= 10) {
++res;
}
return res;
}

template <std::size_t N>
constexpr auto to_char_array()
{
constexpr auto digit_count = count_digit<N>();
std::array<char, digit_count> res{};

auto n = N;
for (std::size_t i = 0; i != digit_count; ++i) {
res[digit_count - 1 - i] = static_cast<char>('0' + n % 10);
n /= 10;
}
return res;
}

template <std::size_t N>
constexpr std::array<char, N - 1> to_array(const char (&a)[N])
{
std::array<char, N - 1> res{};

for (std::size_t i = 0; i != N - 1; ++i) {
res[i] = a[i];
}
return res;
}

template <std::size_t ...Ns>
constexpr std::array<char, (Ns + ...)> concat(const std::array<char, Ns>&... as)
{
std::array<char, (Ns + ...)> res{};
std::size_t i = 0;
auto l = [&](const auto& a) { for (auto c : a) {res[i++] = c;} };

(l(as), ...);
return res;
}

And finally:

template<int size>
class MyClass
{
public:
void onError()
{
constexpr auto errMsg = concat(to_array("Error in MyClass of size "),
to_char_array<size>(),
to_array(": Detailed error description\n"),
std::array<char, 1>{{0}});

std::cout << errMsg.data();
}
};

Demo

c++ compile-time string concatenation using boost-mpl

OK, as per your final edits, I see several problems here.

First, you can use mpl::push_back to add an element to a sequence. Now you're concatenating two sequences. I changed the type of type<>::value to mpl::char_, then changed the order of the mpl::push_back arguments (first the sequence, then the element). Also, you have to use push_front, not push_back in this code. Finally, I added a ::type after the push_front, because you have to extract the actual type here. Here is the code for reference:

using namespace boost;
using namespace std;

template<class A>
struct type {};

template<>
struct type<int> {
typedef mpl::char_ < 'i' > value;
};

template<>
struct type<char> {
typedef mpl::char_ < 'c' > value;
};

struct empty {
};

template<class A, class B, class C, class D>
struct converter;

template<class A, class B = empty, class C = empty, class D = empty>
struct converter {
typedef typename mpl::push_front< typename converter<B,C,D>::value, typename type<A>::value >::type value ;
};

template<>
struct converter<empty, empty, empty, empty> {
typedef mpl::string < '\0' > value;
};

Now this code works as expected:

int
main (void)
{
cout << mpl::c_str< converter<int,char>::value >::value << endl;
return 0;
}

(prints ic).



Related Topics



Leave a reply



Submit