How to declare constexpr C string?
Asked Answered
N

3

37

I think i quite understand how to use the keyword constexpr for simple variable types, but i'm confused when it comes to pointers to values.

I would like to declare a constexpr C string literal, which will behave like

#define my_str "hello"

That means the compiler inserts the C string literal into every place where i enter this symbol, and i will be able to get its length at compile-time with sizeof.

Is it constexpr char * const my_str = "hello";

or const char * constexpr my_str = "hello";

or constexpr char my_str [] = "hello";

or something yet different?

Nikolenikoletta answered 7/9, 2017 at 15:36 Comment(0)
C
42

Is it constexpr char * const my_str = "hello";

No, because a string literal is not convertible to a pointer to char. (It used to be prior to C++11, but even then the conversion was deprecated).

or const char * constexpr my_str = "hello";

No. constexpr cannot go there.

This would be well formed:

constexpr const char * my_str = "hello";

but it does not satify this:

So that i will be able to get its length at compile-time with sizeof, etc.


or constexpr char my_str [] = "hello";

This is well formed, and you can indeed get the length at compile time with sizeof. Note that this size is the size of the array, not the length of the string i.e. the size includes the null terminator.

Clairclairaudience answered 7/9, 2017 at 15:47 Comment(2)
I understand the value of explaining the actual type, but why not proposing using simply constexpr auto?Hauger
is constexpr auto my_str = "hello"; okay?Hauger
M
27

In C++17, you can use std::string_view and string_view_literals

using namespace std::string_view_literals;
constexpr std::string_view my_str = "hello, world"sv;

Then,

my_str.size() is compile time constant.

Moiramoirai answered 7/9, 2017 at 15:44 Comment(2)
Note that, in general, string_view::data does not guarantee a null-terminated string.Calices
In general, yes. But in this particular case, the string_view is referring to a null-terminated string literal, so data() will be null-terminated.Porky
T
0

C++11

Due to the null-termination of constexpr const char* string literals, you can simply use std::strlen, and if it ought to work compile-time, a custom function is easily definable:

constexpr std::size_t _strlen(char const* s, std::size_t count = 0) {
    return (*s == '\0') ? count : _strlen(s + 1, count + 1);
}

(C++11 constexpr functions can only contain exactly one return statement, so use recursion.)

If sizeof has to additionally work like on arrays (i.e. included the null-termination), you can define string literals using variadic templates like so:

constexpr std::size_t _strlen(char const* s, std::size_t count = 0) {
    return (*s == '\0') ? count : _strlen(s + 1, count + 1);
}

// Parameter pack helpers (similar to C++14)
template<std::size_t ...> struct _index_sequence { using type = _index_sequence; };
template<std::size_t N, std::size_t ... S> struct gen_pack_sequence: gen_pack_sequence<N - 1, N - 1, S...> {};
template<std::size_t ... S> struct gen_pack_sequence<0, S...> : _index_sequence<S...> {};
template<std::size_t N> using _make_index_sequence = typename gen_pack_sequence<N>::type;

template<char ... Chars> struct char_sequence {
    static constexpr char value[] = { Chars... };
    template<template<char...> class Template> using param_pack = Template<Chars...>;
};
template<char ... Chars> constexpr char char_sequence<Chars...>::value[];
template<char ... Chars> struct char_string: char_sequence<Chars..., '\0'> {};

// Compile-time string literals ; Concept: https://ideone.com/QvXuYf
template<typename S, typename T> struct _struct_to_string;
template<typename S, std::size_t ... Indices> struct _struct_to_string<S, _index_sequence<Indices...>> { typedef char_string<S::get()[Indices] ...> type; };
template<typename S> struct struct_to_string { typedef _make_index_sequence<_strlen(S::get())> indices; typedef typename _struct_to_string<S, indices>::type type; };
#define CONSTEXPR_STRING(name, s) \
    struct name ## __struct { constexpr static const char* get() { return s; } }; \
    typedef struct_to_string<name ## __struct>::type name

Usage with output:

CONSTEXPR_STRING(var, "Hello!");

constexpr const char* _var = var::value;
constexpr std::size_t _varlen = _strlen(_var);

int main()
{
    std::cout << _var << std::endl; // "Hello!" has length 6
    std::cout << _varlen << std::endl; // 6
    std::cout << _strlen(_var) << std::endl; // 6
    std::cout << std::strlen(_var) << std::endl; // 6
    std::cout << sizeof(var::value) << std::endl; // 7: size of { 'H', 'e', 'l', 'l', 'o', '!', '\0' }
    std::cout << sizeof(_var) << std::endl; // 8: a pointer is 64 bits = 8 bytes
}

This might seem overkill for one could simply use an array like

constexpr char _var[] = "hello";

but using ::value circumvents the problems that C-style arrays cannot be return values of functions and there are no constexpr parameters in C++. The char_string type can be extended to support all possible constexpr string features and is in particular flexible via using ::param_pack, which comes in handy for example w.r.t. compile-time string concatenation. (Here is an example of how it can be used.)

Tangled answered 25/5, 2023 at 18:22 Comment(2)
Why use recursion for this? It is painful in debug builds.Honor
@AngelicosPhosphoros I found the reference on cppreference and linked it in my answer as well.Tangled

© 2022 - 2024 — McMap. All rights reserved.