constexpr length of a string from template paramet

2019-06-06 03:00发布

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.

2条回答
别忘想泡老子
2楼-- · 2019-06-06 03:31

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.

查看更多
地球回转人心会变
3楼-- · 2019-06-06 03:37
constexpr std::size_t length( const char * str ) {
  return (!str||!*str)?0:(1+length(str+1));
}

template<const char * String>
constexpr size_t len() { return length(String); }

extern constexpr const char HELLO[] = "Hello World!!!";

live example. Recursion wouldn't be needed in C++14.

查看更多
登录 后发表回答