Generating simple format string at compile time - c++

I'm trying to generate a simple format string for fmt at compile time, but I can't quite figure out the string concatenation. I'm limited to c++ 14.
What I'd like is to be able to generate a format string for N items, so it could be used as follows:
auto my_string = fmt::format(format_string_struct<2>::format_string, "Item", "key1", "value1", "key2", "value2");
The generated format string would be: "{} {}={} {}={}", resulting in a formatted string of "Item key1=value1 key2=value2". The base of the format string would be "{}", and it appends a " {}={}" for each N.
I'm trying to use recursive templates, but I just can't quite get everything to work. I got some code from Concatenate compile-time strings in a template at compile time? for concatenating strings (though I get a undefined reference to `concat_impl<&base_format_string_struct::format_string, std::integer_sequence<int, 0, 1>, &format_string_parameter_struct::parameter_string, std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7> >::value' error)
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>
constexpr auto concat {
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, params...> {
};
template <int arg_count>
struct format_string_struct:
public format_string_struct<arg_count - 1, format_string_parameter_struct> {
};
template <const char[]... params>
struct format_string_struct<0> {
static const char* format_string() {
return concat<base_format_string_struct, params...>;
}
};

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

Related

How to compose a string literal from constexpr char arrays at compile time?

I'm trying to create a constexpr function that concatenates const char arrays to one array. My goal is to do this recursively by refering to a specialized variable template join that always joins two const char*'s . But the compiler doesn't like it and throws an error message that I can't get behind.
I've already checked out this topic but it unfortunately doesn't have a straight up answer.
Code:
#include <type_traits>
#include <cstdio>
#include <iostream>
constexpr auto size(const char*s)
{
int i = 0;
while(*s!=0) {
++i;
++s;
}
return i;
}
template <const char* S1, typename, const char* S2, typename>
struct join_impl;
template <const char* S1, int... I1, const char* S2, int... I2>
struct join_impl<S1, std::index_sequence<I1...>, S2, std::index_sequence<I2...>>
{
static constexpr const char value[]{ S1[I1]..., S2[I2]..., 0 };
};
template <const char* S1, const char* S2>
constexpr auto join
{
join_impl<S1, std::make_index_sequence<size(S1)>, S2, std::make_index_sequence<size(S2)>>::value
};
template <const char* S1, const char* S2, const char*... S>
struct join_multiple
{
static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
};
template <const char* S1, const char* S2>
struct join_multiple<S1, S2>
{
static constexpr const char* value = join<S1, S2>;
};
constexpr const char a[] = "hello";
constexpr const char b[] = "world";
constexpr const char c[] = "how is it going?";
int main()
{
// constexpr size_t size = 100;
// char buf[size];
// lw_ostream{buf, size};
std::cout << join_multiple<a, b, c>::value << std::endl;
}
Error:
<source>:33:82: error: qualified name refers into a specialization of variable template 'join'
static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
<source>:25:16: note: variable template 'join' declared here
constexpr auto join
^
<source>:33:34: error: default initialization of an object of const type 'const char *const'
static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
^
= nullptr
2 errors generated.
ASM generation compiler returned: 1
<source>:33:82: error: qualified name refers into a specialization of variable template 'join'
static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
<source>:25:16: note: variable template 'join' declared here
constexpr auto join
^
<source>:33:34: error: default initialization of an object of const type 'const char *const'
static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>::value;
^
= nullptr
2 errors generated.
Execution build compiler returned:
What am I missing?
As alternative, to avoid to build the temporary char arrays, you might work with types (char sequences) and create the char array variable only at the end, something like:
constexpr auto size(const char*s)
{
int i = 0;
while(*s!=0) {
++i;
++s;
}
return i;
}
template <const char* S, typename Seq = std::make_index_sequence<size(S)>>
struct as_sequence;
template <const char* S, std::size_t... Is>
struct as_sequence<S, std::index_sequence<Is...>>
{
using type = std::integer_sequence<char, S[Is]...>;
};
template <typename Seq>
struct as_string;
template <char... Cs1>
struct as_string<std::integer_sequence<char, Cs1...>>
{
static constexpr const char c_str[] = {Cs1..., '\0'};
};
template <typename Seq1, typename Seq2, typename... Seqs>
struct join_seqs
{
using type = typename join_seqs<typename join_seqs<Seq1, Seq2>::type, Seqs...>::type;
};
template <char... Cs1, char... Cs2>
struct join_seqs<std::integer_sequence<char, Cs1...>, std::integer_sequence<char, Cs2...>>
{
using type = std::integer_sequence<char, Cs1..., Cs2...>;
};
template <const char*... Ptrs>
const auto join =
as_string<typename join_seqs<typename as_sequence<Ptrs>::type...>::type>::c_str;
Demo
There are two issues here.
First, join is a template variable, so it does not contain the so-called value_type, it itself is a value, so your join_multiple should be
template <const char* S1, const char* S2, const char*... S>
struct join_multiple {
static constexpr const char* value = join<S1, join_multiple<S2, S...>::value>;
};
Second and less important, the integer type of index_sequence is size_t instead of int, so the partial specialization of join_impl should be (this is not necessary, but using a type other than size_t will cause GCC to reject it incorrectly)
template <const char* S1, size_t... I1, const char* S2, size_t... I2>
struct join_impl<S1, std::index_sequence<I1...>, S2, std::index_sequence<I2...>> {
static constexpr const char value[]{ S1[I1]..., S2[I2]..., 0 };
};
Demo

Converting aggregate initialization of a struct containing a CONST char array[N] member into a constructor

Given a simple struct containing a const char array, this can be easily initialized via aggregate initialization:
struct int_and_text {
constexpr static int Size = 8;
const int i;
const char text[Size];
};
const int_and_text some_data[] = { { 0, "abc"}, { 77, "length8" } };
But now I want to add a constructor, but so far nothing I tried worked, even one using a constexpr memcpy-ish variant.
template <std::size_t N>
int_and_text(int i_, const char (&text_)[N])
: i{i_},
text{" ? " /* const text[8] from const text[1-8] */ }
{ static_assert(N <= Size); }
Is this possible? A const char text_[8] constructor argument seems to decay into a char*. In the long term making everything constexpr would be nice as well.
#include <cstddef>
#include <utility>
class int_and_text
{
public:
template <std::size_t N>
int_and_text(int i_, const char (&text_)[N])
: int_and_text(i_, text_, std::make_index_sequence<N>{})
{
}
private:
template <std::size_t N, std::size_t... Is>
int_and_text(int i_, const char (&text_)[N], std::index_sequence<Is...>)
: i{i_}
, text{text_[Is]...}
{
static_assert(N <= Size);
}
constexpr static int Size = 8;
const int i;
const char text[Size];
};
const int_and_text some_data[] = { {0, "abc"}, {77, "length8"} };
DEMO

Building static strings with C++ Metaprogramming

I'm writing an open source sqlite interface library (mSqliteCpp for instance) that uses c++ classes and types to correctly manage sqlite databases.
So far the library is heavily using TMP to build SQL strings, but I found a possible issue that may somehow affect the efficiency of the library.
I'm using classes to manage Fields definitions, classes definitions, queries and statements. Basically to define a table or a SELECT statement, one defines the fields using proper FieldDef<T> objects, then pass them to a statement builder that returns the correctly formatted SQL statement.
For example:
auto fldId = sqlite::makeFieldDef("id", sqlite::FieldType::Integer());
auto fldName = sqlite::makeFieldDef("name", sqlite::FieldType::Text());
auto fldValue = sqlite::makeFieldDef("value", sqlite::FieldType::Real());
sqlite::statements::Select select("Table", fldId, fldName, fldValue);
ASSERT_EQ(select.string(), "SELECT id,name,value FROM Table;");
Note that each field is strongly typed by the FieldType type passed to the makeFieldDef function, but different fields with similar type can be exchanged. For this reason the SQL statement in the ASSERT call is built at runtime.
I would like to have them built at compile time, at least under certain conditions. For example, developer could use a different class or maybe the constexpr keyword. But at this time I've no idea if this could be achieved.
What are the possible patterns? Can strings be statically built through TMP? I'm using C++11/14.
Thank you.
what I'm looking for is a solution that will make most of the computation with strings at compile time [...] At the moment I'm just courious about how this could be implemented.
Not an answer to your sqlite question but... if your "computation with strings at compile time" are simple as concatenation... just to play with constexpr and template metaprogramming...
std::string isn't constexpr but std::array can be.
So you can define a fake string as an alias for an array of chars.
template <std::size_t N>
using fakeStr = std::array<char, N>;
You can convert a string literal to a fake string as follows (with an helper function and playing with type traits)
template <std::size_t N, std::size_t ... Is>
constexpr fakeStr<sizeof...(Is)> mFSh (char const (& str)[N],
std::index_sequence<Is...> const &)
{ return { { str[Is]... } }; }
template <std::size_t N>
constexpr auto makeFakeStr (char const (& str)[N])
{ return mFSh(str, std::make_index_sequence<N-1>{}); }
The fake string concatenation is simple as follows
template <std::size_t N1, std::size_t ... I1s,
std::size_t N2, std::size_t ... I2s>
constexpr fakeStr<N1+N2> cFSh (fakeStr<N1> const & s1,
std::index_sequence<I1s...> const &,
fakeStr<N2> const & s2,
std::index_sequence<I2s...> const &)
{ return { { s1[I1s]..., s2[I2s]... } }; }
template <std::size_t N1, std::size_t N2>
constexpr auto concatFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return cFSh(s1, std::make_index_sequence<N1>{},
s2, std::make_index_sequence<N2>{}); }
and a constexpr comparison can be done as follows (with C++17 the helper function can be simpler)
template <std::size_t N1, std::size_t N2, std::size_t ... Is>
constexpr bool cmpFSh (fakeStr<N1> const & s1,
fakeStr<N2> const & s2,
std::index_sequence<Is...> const &)
{
using unused = bool[];
bool ret { true };
(void) unused { true, ret &= s1[Is] == s2[Is] ... };
return ret;
}
template <std::size_t N1, std::size_t N2>
constexpr bool compareFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return (N1 == N2) && cmpFSh(s1, s2, std::make_index_sequence<N1>{}); }
If you want a std::string from a fakeStr, the following code (not constexpr, obviously) can help
template <std::size_t N, std::size_t ... Is>
std::string fFSh (fakeStr<N> const & s, std::index_sequence<Is...> const &)
{ return { s[Is]..., '\0' }; }
template <std::size_t N>
auto fromFakeString (fakeStr<N> const & s)
{ return fFSh(s, std::make_index_sequence<N>{}); }
The following is a full (C++14) working example
#include <array>
#include <string>
#include <iostream>
#include <type_traits>
template <std::size_t N>
using fakeStr = std::array<char, N>;
template <std::size_t N, std::size_t ... Is>
constexpr fakeStr<sizeof...(Is)> mFSh (char const (& str)[N],
std::index_sequence<Is...> const &)
{ return { { str[Is]... } }; }
template <std::size_t N>
constexpr auto makeFakeStr (char const (& str)[N])
{ return mFSh(str, std::make_index_sequence<N-1>{}); }
template <std::size_t N1, std::size_t ... I1s,
std::size_t N2, std::size_t ... I2s>
constexpr fakeStr<N1+N2> cFSh (fakeStr<N1> const & s1,
std::index_sequence<I1s...> const &,
fakeStr<N2> const & s2,
std::index_sequence<I2s...> const &)
{ return { { s1[I1s]..., s2[I2s]... } }; }
template <std::size_t N1, std::size_t N2>
constexpr auto concatFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return cFSh(s1, std::make_index_sequence<N1>{},
s2, std::make_index_sequence<N2>{}); }
template <std::size_t N1, std::size_t N2, std::size_t ... Is>
constexpr bool cmpFSh (fakeStr<N1> const & s1,
fakeStr<N2> const & s2,
std::index_sequence<Is...> const &)
{
using unused = bool[];
bool ret { true };
(void) unused { true, ret &= s1[Is] == s2[Is] ... };
return ret;
}
template <std::size_t N1, std::size_t N2>
constexpr bool compareFakeStr (fakeStr<N1> const & s1, fakeStr<N2> const & s2)
{ return (N1 == N2) && cmpFSh(s1, s2, std::make_index_sequence<N1>{}); }
template <std::size_t N, std::size_t ... Is>
std::string fFSh (fakeStr<N> const & s, std::index_sequence<Is...> const &)
{ return { s[Is]..., '\0' }; }
template <std::size_t N>
auto fromFakeString (fakeStr<N> const & s)
{ return fFSh(s, std::make_index_sequence<N>{}); }
int main()
{
constexpr auto f1 = makeFakeStr("abcd");
constexpr auto f2 = makeFakeStr("xyz");
constexpr auto f12 = concatFakeStr(f1, f2);
constexpr auto f3 = makeFakeStr("abcdxyz");
static_assert( true == compareFakeStr(f12, f3), "!" );
static_assert( false == compareFakeStr(f12, f1), "!" );
auto s12 = fromFakeString(f12);
std::cout << s12 << std::endl;
}
I repeat: just to play.
#max66's solution is probably better, but there is a proof of concept of a different approach: to store strings as char parameter packs:
template <char ...C> struct cexpr_str
{
static constexpr char value[] {C..., '\0'};
};
Here is a complete example, demonstrating how to create such strings from string literals, concatenate, and compare them:
#include <cstddef>
#include <iostream>
#include <type_traits>
#include <utility>
template <char ...C> struct cexpr_str
{
static constexpr char value[] {C..., '\0'};
};
namespace impl
{
using std::size_t;
template <typename ...P> struct cexpr_cat
{
using type = cexpr_str<>;
};
template <typename A, typename B, typename ...P> struct cexpr_cat<A, B, P...>
{
using type = typename cexpr_cat<typename cexpr_cat<A, B>::type, P...>::type;
};
template <char ...A, char ...B> struct cexpr_cat<cexpr_str<A...>, cexpr_str<B...>>
{
using type = cexpr_str<A..., B...>;
};
template <typename T> struct cexpr_cat<T>
{
using type = T;
};
template <typename, typename> struct cexpr_str_subseq {};
template <size_t ...S, char ...C> struct cexpr_str_subseq<std::index_sequence<S...>, cexpr_str<C...>>
{
using type = cexpr_str<cexpr_str<C...>::value[S]...>;
};
template <size_t N> constexpr char cexpr_str_at(const char (&str)[N], size_t pos)
{
if (pos < 0 || pos >= N)
return '\0';
else
return str[pos];
}
}
template <typename ...P> using cexpr_cat = typename impl::cexpr_cat<P...>::type;
#define MAKE_CEXPR_STR(x) ::impl::cexpr_str_subseq<::std::make_index_sequence<sizeof x - 1>,\
::cexpr_str<CEXPR_STR_16(0,x),CEXPR_STR_16(16,x),CEXPR_STR_16(32,x),CEXPR_STR_16(48,x)>>::type
#define CEXPR_STR_16(s,x) CEXPR_STR_4(s,x),CEXPR_STR_4(s+4,x),CEXPR_STR_4(s+8,x),CEXPR_STR_4(s+12,x)
#define CEXPR_STR_4(s,x) ::impl::cexpr_str_at(x,s),::impl::cexpr_str_at(x,s+1),::impl::cexpr_str_at(x,s+2),::impl::cexpr_str_at(x,s+3)
int main()
{
// You can use this macro to create a string.
// Note that it limits the length to 64, but can be easily rewritten to allow larger values.
using A = MAKE_CEXPR_STR("Hello,");
// If you don't want to use that macro, you can do this.
using B = cexpr_str<'w','o','r','l','d'>;
// You can concat any number of comma-separated strings, not just two.
using C = cexpr_cat<A,B>;
// This prints: `Hello,`+`world`=`Hello,world`.
std::cout << "`" << A::value << "`+`" << B::value << "`=`" << C::value << "`\n";
// You can use std::is_same[_v] to compare those strings:
std::cout << std::is_same_v<B,A> << '\n'; // Prints 0.
std::cout << std::is_same_v<B,MAKE_CEXPR_STR("world")> << '\n'; // Prints 1.
}

constexpr to concatenate two or more char strings

I'm trying to make a constexpr function that will concatenate an arbitrary number of char arrays by working from the following answer by Xeo, which concatenates two char arrays.
https://stackoverflow.com/a/13294458/1128289
#include <array>
template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2], seq<I1...>, seq<I2...>){
return {{ a1[I1]..., a2[I2]... }};
}
template<unsigned N1, unsigned N2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2]){
return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
My attempt thus far:
#include <iostream>
#include <array>
template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};
template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr const std::array<char, N1+N2-1>
concat_impl(
const char (&a1)[N1], const char (&a2)[N2], seq<I1...>, seq<I2...>)
{
return {{ a1[I1]..., a2[I2]... }};
}
template<unsigned N1, unsigned N2>
constexpr const std::array<char, N1+N2-1>
concat(const char (&a1)[N1], const char (&a2)[N2])
{
return concat_impl(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}
template<unsigned N1, unsigned N2, class... Us>
constexpr auto
concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs)
-> std::array<char, N1 + decltype(concat(a2, xs...))::size() - 1>
{
return concat(a1, concat(a2, xs...));
}
int main()
{
auto const s = concat("hi ", "there!");
std::cout << s.data() << std::endl;
// compile error:
auto const t = concat("hi ", "there ", "how ", "are ", "you?");
std::cout << t.data() << std::endl;
}
Both gcc 4.9 and clang 3.5 give errors indicating that no function matching the concat inside the decltype expression can be found.
clang:
error: no matching function for call to 'concat'
auto const t = concat("hi ", "there ", "how ", "are ", "you?");
^~~~~~
ctconcat.cpp:105:16: note: candidate template ignored: substitution failure [with N1 = 4, N2 = 7, Us = <char [5], char [5], char [5]>]: no matching function for call to 'concat'
constexpr auto concat(const char(&a1)[N1], const char(&a2)[N2], const Us&... xs) -> std::array<char, N1 + decltype(concat(a2, xs...))::size() - 1>
^ ~~~~~~
ctconcat.cpp:62:43: note: candidate function template not viable: requires 2 arguments, but 5 were provided
constexpr const std::array<char, N1+N2-1> concat(const char (&a1)[N1], const char (&a2)[N2])
^
1 error generated.
The errors from gcc and clang both indicate that the second concat function template is not a candidate for the concat in the decltype expression. Only the first template is considered. Why is that and how do I fix this?
Edit: Relevant question on why decltype can't be used recursively
trailing return type using decltype with a variadic template function
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
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;
}

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

Currently I have:
template <typename T> struct typename_struct<T*> {
static char const* name() {
return (std::string(typename_struct<T>::name()) + "*").c_str();
}
};
I wonder if I can avoid the whole bit where I'm forced to allocate a string to perform the concatenation.
This is all happening at compile time, i.e. I intend to get the string "int****" when I reference typename_struct<int****>::name(). (Do assume that I have declared a corresponding specialization for int which returns "int")
As the code is written now, does the compiler do the concatenation with std::string during compile time only? (I would be okay with that) Or does such a call result in 4 std::string based concatenations at runtime? (I would not be okay with that)
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;
}
Alternative way without using recursive templates (but requires C++14):
#include <utility>
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>
constexpr auto concat {
concat_impl<S1, make_is<size(S1)>, S2, make_is<size(S2)>>::value
};
Example:
constexpr const char a[] = "int";
constexpr const char c[] = "**";
#include <iostream>
int main()
{
std::cout << concat<a,b> << '\n';
}
append characters to string can also be implemented like this, by replacing the second const char* parameter with char....
I'm not sure of what you're searching for but I believe you're interested in a combination of typeid and name-demangling (which compiler are you using?)
In gcc it would be something like
#include<iostream>
#include <string>
#include <typeinfo>
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
using namespace std;
std::string demangle(const char* name) {
int status = -4; // some arbitrary value to eliminate the compiler warning
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};
return (status==0) ? res.get() : name ;
}
template <typename T> struct typename_struct {
static std::string name() {
std::string typeName = typeid(T).name();
return demangle(typeName.c_str());
}
};
int main(){
cout << typename_struct<int****>::name(); // Prints "int****"
return 0;
}
http://ideone.com/nLsFF0
Sources: https://stackoverflow.com/a/4541470/1938163
As for your question: those aren't constexpr constructs, thus the evaluation happens at runtime although the templated parameters and code are instantiated at compile-time.
Using templates doesn't mean every instruction contained in there will be executed and resolved at "compile-time".
I believe you can't achieve this whole bunch of stuff at compile-time since there are de-mangling functions (ABI-specific) involved. If I interpreted your question wrong please let me know.
#include <iostream>
//
***************************************************************************
template<const char* S1, const char* S2, size_t I1 = 0, size_t I2 = 0, char = S1[I1], char = S2[I2], char... Chars>
struct Concat : Concat<S1, S2, I1 + 1, I2, S1[I1 + 1], S2[I2], Chars..., S1[I1]>
{
};
// ****************************************************************************
template<const char* S1, const char* S2, size_t I1, size_t I2, char C2, char... Chars>
struct Concat<S1, S2, I1, I2, 0, C2, Chars...> : Concat<S1, S2, I1, I2 + 1, 0, S2[I2 + 1], Chars..., S2[I2]>
{
};
// ****************************************************************************
template<const char* S1, const char* S2, size_t N1, size_t N2, char... Chars>
struct Concat<S1, S2, N1, N2, 0, 0, Chars...>
{
static constexpr const char Text[] = { Chars... , 0 };
};
// ****************************************************************************
static constexpr const char A[] = "123";
static constexpr const char B[] = "456";
// ****************************************************************************
int main(int argc, char* argv[]){
std::cout << Concat<A, B>::Text << std::endl;
return 0;
}