Convert a Number to a String Literal with Constexpr

Convert a number to a string literal with constexpr

Variadic templates to the rescue. :)

namespace detail
{
template<unsigned... digits>
struct to_chars { static const char value[]; };

template<unsigned... digits>
constexpr char to_chars<digits...>::value[] = {('0' + digits)..., 0};

template<unsigned rem, unsigned... digits>
struct explode : explode<rem / 10, rem % 10, digits...> {};

template<unsigned... digits>
struct explode<0, digits...> : to_chars<digits...> {};
}

template<unsigned num>
struct num_to_string : detail::explode<num> {};

As always, here's a live example on Coliru showing usage and the (relevant) generated assembly.


It's straightforward to adapt this approach to support negative numbers as well. Here's a more generic form that requires the user to input the integer's type:

namespace detail
{
template<uint8_t... digits> struct positive_to_chars { static const char value[]; };
template<uint8_t... digits> constexpr char positive_to_chars<digits...>::value[] = {('0' + digits)..., 0};

template<uint8_t... digits> struct negative_to_chars { static const char value[]; };
template<uint8_t... digits> constexpr char negative_to_chars<digits...>::value[] = {'-', ('0' + digits)..., 0};

template<bool neg, uint8_t... digits>
struct to_chars : positive_to_chars<digits...> {};

template<uint8_t... digits>
struct to_chars<true, digits...> : negative_to_chars<digits...> {};

template<bool neg, uintmax_t rem, uint8_t... digits>
struct explode : explode<neg, rem / 10, rem % 10, digits...> {};

template<bool neg, uint8_t... digits>
struct explode<neg, 0, digits...> : to_chars<neg, digits...> {};

template<typename T>
constexpr uintmax_t cabs(T num) { return (num < 0) ? -num : num; }
}

template<typename Integer, Integer num>
struct string_from : detail::explode<(num < 0), detail::cabs(num)> {};

Its usage is like:

string_from<signed, -1>::value

as demonstrated in the live example on Coliru.

C++ convert integer to string at compile time

This can be done using C++14 without any external dependencies. The key addition to the standard is the ability to have non-trivial constexpr constructors, allowing the functionality to be contained within a simple class.

Given an integer template parameter, the constructor can carry out the integer-to-string conversion. This is stored in a member character buffer whose size is determined by an additional constexpr function. Then, a user-defined conversion provides access to the buffer:

#include <cstdint>

template<std::intmax_t N>
class to_string_t {

constexpr static auto buflen() noexcept {
unsigned int len = N > 0 ? 1 : 2;
for (auto n = N; n; len++, n /= 10);
return len;
}

char buf[buflen()] = {};

public:
constexpr to_string_t() noexcept {
auto ptr = buf + buflen();
*--ptr = '\0';

if (N != 0) {
for (auto n = N; n; n /= 10)
*--ptr = "0123456789"[(N < 0 ? -1 : 1) * (n % 10)];
if (N < 0)
*--ptr = '-';
} else {
buf[0] = '0';
}
}

constexpr operator const char *() const { return buf; }
};

Finally, a variable template (another C++14 addition) simplifies the syntax:

template<std::intmax_t N>
constexpr to_string_t<N> to_string;

puts(to_string<62017>); // prints "62017"

The functionality can be extended to support other bases (e.g. hexadecimal), wide character types, and the common container interface; I've packed this all into a single header and put it on GitHub at tcsullivan/constexpr-to-string.

With C++20, this can also be extended to support floating-point numbers. A container type is needed for the floating-point literal, which previously could not be a template parameter. See the f_to_string.hpp header in the GitHub repo for the implementation.

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

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

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++ constexpr std::array of string literals

As user17732522 already noted, the type deduction for your original code produces a const std::array<const char*, 3>. This works, but it's not a C++ std::string, so every use needs to scan for the NUL terminator, and they can't contain embedded NULs. I just wanted to emphasize the suggestion from my comment to use std::string_view.

Since std::string inherently relies on run-time memory allocation, you can't use it unless the entirety of the associated code is also constexpr (so no actual strings exist at all at run-time, the compiler computes the final result at compile-time), and that's unlikely to help you here if the goal is to avoid unnecessary runtime work for something that is partially known at compile time (especially if the array gets recreated on each function call; it's not global or static, so it's done many times, not just initialized once before use).

That said, if you can rely on C++17, you can split the difference with std::string_view. It's got a very concise literal form (add sv as a prefix to any string literal), and it's fully constexpr, so by doing:

// Top of file
#include <string_view>
// Use one of your choice:
using namespace std::literals; // Enables all literals
using namespace std::string_view_literals; // Enables sv suffix only
using namespace std::literals::string_view_literals; // Enables sv suffix only

// Point of use
constexpr std::array myStrings = { "one"sv, "two"sv, "three"sv };

you get something that involves no runtime work, has most of the benefits of std::string (knows its own length, can contain embedded NULs, accepted by most string-oriented APIs), and therefore operates more efficiently than a C-style string for the three common ways a function accepts string data:

  1. For modern APIs that need to read a string-like thing, they accept std::string_view by value and the overhead is just copying the pointer and length to the function
  2. For older APIs that accept const std::string&, it constructs a temporary std::string when you call it, but it can use the constructor that extracts the length from the std::string_view so it doesn't need to prewalk a C-style string with strlen to figure out how much to allocate.
  3. For any API that needs a std::string (because it will modify/store its own copy), they're receiving string by value, and you get the same benefit as in #2 (it must be built, but it's built more efficiently).

The only case where you do worse by using std::string_views than using std::string is case #2 (where if the std::array contained std::strings, no copies would occur), and you only lose there if you make several such calls; in that scenario, you'd just bite the bullet and use const std::array myStrings = { "one"s, "two"s, "three"s };, paying the minor runtime cost to build real strings in exchange for avoiding copies when passing to old-style APIs taking const std::string&.

Why does const have to be added to constexpr for a string literal declaration?

Doesn't constexpr imply const?

It does, on the object being declared, in your case s. The result of applying constexpr is the object

char *const s;

It's still declared to point at a non-const object. Only the address has to be a constant expression. Which means it must be to an object with static storage duration.

Is it possible for something to be constexpr but change in some other way such that is not constant?

No. But then again, it's not the object being declared constexpr that is allowed to change here. For instance

static char foo[] = "abc"; // Not a constant array
constexpr char * s = foo; // But the address is still a valid initializer.

Is a valid pair of declarations.

How does constexpr std::string in C++20 work?

C++20 supports allocation during constexpr time, as long as the allocation is completely deallocated by the time constant evaluation ends. So, for instance, this very silly example is valid in C++20:

constexpr int f() {
int* p = new int(42);
int v = *p;
delete p;
return v;
}

static_assert(f() == 42);

However, if you forget to delete p; there, then f() is no longer a constant expression. Can't leak memory. gcc, for instance, rejects with:

<source>:2:24: error: '(f() == 42)' is not a constant expression because allocated storage has not been deallocated
2 | int* p = new int(42);
| ^

Getting back to your question, std::string will work in constexpr for long strings just fine -- by allocating memory for it as you might expect. However, the C++20 constexpr rules are still limited by this rule that all allocations must be cleaned up by the end of evaluation. Alternatively put, all allocations must be transient - C++ does not yet support non-transient constexpr allocation.

As a result, your original program

int main( )
{
constexpr std::string str { "Where is the constexpr std::string support?"};
}

is invalid, even once gcc supports constexpr string (as it does on trunk right now), because str needs to be destroyed. But this would be fine:

constexpr int f() {
std::string s = "Where is the constexpr std::string support?";
return s.size();
}

static_assert(f() > 16);

whereas it would not have compiled in C++17.


There still won't be support for non-transient constexpr allocation in C++23. It's a surprisingly tricky problem. But, hopefully soon.

Is it possible with C++20 to have a constexpr function return a tuple of types that have static constexpr array's with a value passed though a macro?

It is possible to store a string literal inside of a template argument. I wrote a class that does exactly that a year or so ago. This allows the tuple creation to be constexpr while also offering a quite nice way of passing the strings. This class can also be used to differentiate between two types using a string without modifying the rest of the templated class, which is very useful if you're using typeless handles.

template <std::size_t N>
struct TemplateStringLiteral {
char chars[N];

consteval TemplateStringLiteral(const char (&literal)[N]) {
// Does anyone know of a replacement of std::copy_n? It's from
// <algorithm>.
std::copy_n(literal, N, chars);
}
};

Using this class, it is possible for us to pass the tuple creation this class with a string literal attached to it. This does, sadly, slightly modify your C::x from using the MEMBER define for the function arguments to using the MEMBER define for the template arguments. It is also perfectly possible to just use normal string literals in the template arguments.

struct C
{
// Essentially just f<"abc", "def">();
static constexpr auto x = f<MEMBER(abc), MEMBER(def)>();
};

For the function f we now want to take the parameters as template arguments. So we'll now use the TemplateStringLiteral class to take in the string literals, while also making it a template parameter pack to allow for any amount of parameters.

template <TemplateStringLiteral... literal>
consteval auto f() {
// I'll talk about A in a second. It just holds our string
return std::tuple<A<literal>...> {};
}

Now that we've got a function f that can create a std::tuple from some amount of string literals passed in through template parameters, we'll just need to define the class A that the tuple holds. We can't pass the string literals directly to std::tuple as it does not have something akin to TemplateStringLiteral. Defining class A is very simple, as we only need a str field holding our string literal.

template<TemplateStringLiteral literal>
struct A {
static constexpr auto str = std::to_array(literal.chars);
};

So, using TemplateStringLiteral we've got an implementation that's about 16 lines of C++ and is pretty easily understandable imo.

How to declare constexpr C string?

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.



Related Topics



Leave a reply



Submit