Gaspard Petit Gaspard Petit - 1 month ago 18
C++ Question

constexpr length of a string from template parameter

I am trying to obtain the length of a string passed as a template argument using C++11. Here is what I have found so far:

#include <iostream>
#include <cstring>
extern const char HELLO[] = "Hello World!!!";

template<const char _S[]>
constexpr size_t len1() { return sizeof(_S); }

template<const char _S[]>
constexpr size_t len2() { return std::strlen(_S); }

template<const char _S[], std::size_t _Sz=sizeof(_S)>
constexpr size_t len3() { return _Sz-1; }

template<unsigned int _N>
constexpr size_t len5(const char(&str)[_N])
{
return _N-1;
}

int main() {
enum {
l1 = len1<HELLO>(),
// l2 = len2<HELLO>() does not compile
l3 = len3<HELLO>(),
l4 = len3<HELLO, sizeof(HELLO)>(),
l5 = len5(HELLO),
};
std::cout << l1 << std::endl; // outputs 4
// std::cout << l2 << std::endl;
std::cout << l3 << std::endl; // outputs 3
std::cout << l4 << std::endl; // outputs 14
std::cout << l5 << std::endl; // outputs 14
return 0;
}


I am not very surprised with the results, I understand that the size of the array is lost in the case of len1() and len2(), although the information is present at compile time.

Is there a way to pass the information about the size of the string to the template as well? Something like:

template<const char _S[unsigned int _N]>
constexpr size_t len6() { return _N-1; }





[Edit with context and intent]
I gave up trying to concatenate a set of strings at compile time so I am trying to do it at initialization time. Writing something like
a().b().c().str()
would output
"abc"
while
a().b().str()
would output
"ab"


Using templates,
a().b()
creates a type of
B
with a parent type
A
.
a().b().c()
creates a type
C
with a parent type
B
which has a parent type
A
, etc.

Given a type
B
with a parent
A
, this is a unique type and it can have it's own static buffer to hold the concatenation (this is why
l5
isn't good for me). I could then strcpy` each one consecutively in a static buffer. I don't want to use a dynamically allocated buffer because my allocator is not necessarily configured at this point.

The size of that buffer which should be big enough to hold the string associated with
A
and the string associated with
B
is what I am trying to figure out. I can get it to work if I explicitly sizeof() as an extra template parameter (as done with
l4
in the snippet above), but that make the whole code heavy to read and cumbersome to use.




[Edit 2] I marked the answer that was most helpful - but Yakk's answer was also good on gcc but except it did not compile with Visual Studio.

My understanding at this point is that we cannot rely on
const char []
with external linkage to provide their size. It may work locally (if the template is compiled in the same unit as the symbol), but it won't work if the
const char[]
is in a header file to be used in multiple places.

So I gave up on trying to extract the length from the const char* template paramter and decided to live with
l4
where the
sizeof()
is also provided to the template arguments.

For those who are curious how the whole thing turned out, I pasted a full working sample on ideone: http://ideone.com/A0JwO8

I can now write
Path<A>::b::c::path()
and get the corresponding
"b.c"
string in a static buffer at initialization.

Answer

To concatenate string at compile time,
with gnu extension, you may do:

template<typename C, C...cs> struct Chars
{
    using str_type = C[1 + sizeof...(cs)];
    static constexpr C str[1 + sizeof...(cs)] = {cs..., 0};

    constexpr operator const str_type&() const { return str; }
};

template<typename C, C...cs> constexpr C Chars<C, cs...>::str[1 + sizeof...(cs)];

// Requires GNU-extension
template <typename C, C...cs>
constexpr Chars<C, cs...> operator""_cs() { return {}; }

template <typename C, C...lhs, C...rhs>
constexpr Chars<C, lhs..., rhs...>
operator+(Chars<C, lhs...>, Chars<C, rhs...>) { return {}; }

With usage

constexpr auto hi = "Hello"_cs + " world\n"_cs;

std::cout << hi;

Demo

Without gnu extension, you have to use some MACRO to transform literal into char sequence, as I do there.