How to Use Std::String in a Constexpr

Is it possible to use std::string in a constexpr?

No, and your compiler already gave you a comprehensive explanation.

But you could do this:

constexpr char constString[] = "constString";

At runtime, this can be used to construct a std::string when needed.

constexpr std::string in C++20, how does it 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.

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&.

store a string in a constexpr struct

You might do:

template<typename Char, Char... Cs>
struct CharSeq
{
static constexpr const Char s[] = {Cs..., 0}; // The unique address
};

// That template uses the extension
template<typename Char, Char... Cs>
constexpr CharSeq<Char, Cs...> operator"" _cs() {
return {};
}

See my answer from String-interning at compiletime for profiling to have MAKE_STRING macro if you cannot used the extension (Really more verbose, and hard coded limit for accepted string length).

Then

struct A
{
template <char ... Cs>
constexpr A(CharSeq<char, Cs...>) : m_name(CharSeq<char, Cs...>::s) {}

constexpr auto name(){ return m_name; }

std::string_view m_name;
};

With only valid usages similar to:

A a = {"Hello"_cs};
constexpr A b = {"World"_cs};

Can I create constexpr strings with functions?

Can I create constexpr strings with functions?

You can't return std::string, cause it allocates memory.

Can I make these as a constexpr ?

Sure, something along:

#include <array>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

template<size_t N> constexpr
auto create_header(const char (&comment_symbol)[N]) {
const char one[] = " Begin of File.\n";
const char two[] = " -\n";
std::array<char,
N + (sizeof(one) - 1) +
N + (sizeof(two) - 1) + 1
> ret{};
auto it = ret.begin();
for (const char *i = comment_symbol; *i; ++i) *it++ = *i;
for (const char *i = one; *i; ++i) *it++ = *i;
for (const char *i = comment_symbol; *i; ++i) *it++ = *i;
for (const char *i = two; *i; ++i) *it++ = *i;
return ret;
}

std::string c_header() {
constexpr auto a = create_header("//");
return std::string{a.begin(), a.end()};
}

int main() {
std::cout << c_header() << '\n';
}

How to create a constexpr std::vector std::string or something similar?

std::vector/std::string doesn't have constexpr constructor before C++20...
and even in C++20, the constexpr allocation should not escape from constexpr evaluation, so cannot be used in runtime (for printing).

I don't see a standard constexpr way to transform an integer to a char sequence representation.
std::to_string, std::to_chars, std::format are not constexpr.

So instead of homogeneous container, you might use std::tuple, something like (C++17):

template <std::size_t I>
constexpr auto fizzbuzz_elem()
{
if constexpr (I % 5 == 0 && I % 3 == 0) {
return "FizzBuzz";
} else if constexpr (I % 5 == 0) {
return "Buzz";
} else if constexpr (I % 3 == 0){
return "Fizz";
} else {
return I;
}
}

template <std::size_t...Is>
constexpr auto fizzbuzz_impl(std::index_sequence<Is...>){
return std::make_tuple(fizzbuzz_elem<1 + Is>()...);
}

template <std::size_t N>
constexpr auto fizzbuzz(){
return fizzbuzz_impl(std::make_index_sequence<N>());
}

int main() {
constexpr auto res = fizzbuzz<42>();
std::apply([](auto... e){ ((std::cout << e << std::endl), ...); }, res);
}

Demo

c++ constexpr concatenate char*

From constexpr you cannot return char* which is constructed there... You must return some compile time known(also its size) constant thingy.
A possible solution could be something like:

#include <cstring>

// Buffer to hold the result
struct NameBuffer
{
// Hardcoded 128 bytes!!!!! Carefully choose the size!
char data[128];
};

// Copy src to dest, and return the number of copied characters
// You have to implement it since std::strcpy is not constexpr, no big deal.
constexpr int constexpr_strcpy(char* dest, const char* src);

//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr NameBuffer name()
{
// We will return this
NameBuffer buf{};

constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";

// Copy them one by one after each other
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
// Null terminate the buffer, or you can store the size there or whatever you want
buf.data[n] = '\0';
return buf;
}

Demo

And since the returned char* is only depends on the template parameter in your case, you can create templated variables, and create a char* to them, and it can act like any other char*...

EDIT:

I have just realized that your pseudo code will never work!! Inside name<T>() you are trying to call name<T>().

You must redesign this!!! But! With some hack you can determine the size at compile time somehow for example like this:

#include <cstring>
#include <iostream>

template<std::size_t S>
struct NameBuffer
{
char data[S];
};

// Copy src to dest, and return the number of copied characters
constexpr int constexpr_strcpy(char* dest, const char* src)
{
int n = 0;
while((*(dest++) = *(src++))){ n++; }
return n;
}

// Returns the len of str without the null term
constexpr int constexpr_strlen(const char* str)
{
int n = 0;
while(*str) { str++; n++; }
return n;
}

// This template parameter does nothing now...
// I left it there so you can see how to create the template variable stuff...
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr auto createName()
{
constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";

constexpr std::size_t buff_size = constexpr_strlen(collectionName) +
constexpr_strlen(containedTypeName) +
2; // +1 for <, +1 for >

/// +1 for the nullterm
NameBuffer<buff_size + 1> buf{};

/// I'm lazy to rewrite, but now we already calculated the lengths...
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
buf.data[n] = '\0';
return buf;
}

// Create the buffer for T
template<typename T>
static constexpr auto name_buff_ = createName<T>();

// point to the buffer of type T. It can be a function too as you wish
template<typename T>
static constexpr const char* name = name_buff_<T>.data;

int main()
{
// int is redundant now, but this is how you could use this
std::cout << name<int> << '\n';
return 0;
}

Demo



Related Topics



Leave a reply



Submit