28

Apparently, the constexpr std::string has not been added to libstdc++ of GCC yet (as of GCC v11.2).

This code:

#include <iostream>
#include <string>

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

    std::cout << str << '\n';
}

does not compile:

time_measure.cpp:37:31: error: the type 'const string' {aka 'const std::__cxx11::basic_string<char>'} of 'constexpr' variable 'str' is not literal
   37 |         constexpr std::string str { "Where is the constexpr std::string support?"};
      |                               ^~~
In file included from c:\mingw64\include\c++\11.2.0\string:55,
                 from c:\mingw64\include\c++\11.2.0\bits\locale_classes.h:40,
                 from c:\mingw64\include\c++\11.2.0\bits\ios_base.h:41,
                 from c:\mingw64\include\c++\11.2.0\ios:42,
                 from c:\mingw64\include\c++\11.2.0\ostream:38,
                 from c:\mingw64\include\c++\11.2.0\iostream:39,
                 from time_measure.cpp:2:
c:\mingw64\include\c++\11.2.0\bits\basic_string.h:85:11: note: 'std::__cxx11::basic_string<char>' is not literal because:
   85 |     class basic_string
      |           ^~~~~~~~~~~~
c:\mingw64\include\c++\11.2.0\bits\basic_string.h:85:11: note:   'std::__cxx11::basic_string<char>' does not have 'constexpr' destructor

How will such strings work under the hood when a string contains more than 16 chars (because GCC's SSO buffer size is 16)? What would be a brief explanation? Will a trivial constructor create the string object on the stack and never use dynamic allocations?

This code:

    std::cout << "is_trivially_constructible: "
              << std::boolalpha << std::is_trivially_constructible<const std::string>::value << '\n';

prints this:

is_trivially_constructible: false

Now by using constexpr here (obviously does not compile with GCC v11.2):

    std::cout << "is_trivially_constructible: "
              << std::boolalpha << std::is_trivially_constructible<constexpr std::string>::value << '\n';

will the result be true like below?

is_trivially_constructible: true

My goal

My goal was to do something like:

    constexpr std::size_t a { 4 };
    constexpr std::size_t b { 5 };
    constexpr std::string msg { std::format( "{0} + {1} == {2}", a, b, a + b ) };

    std::cout << msg << '\n';

Neither std::format nor constexpr std::string compile on GCC v11.2.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
digito_evo
  • 3,216
  • 2
  • 14
  • 42
  • 3
    *Will a trivial constructor create the string object on the stack and never use dynamic allocations?* There's no need for that, as dynamic allocations are permitted in constant expressions, so long as it's all deallocated before the end of the constant evaluation. – cigien Jan 03 '22 at 21:34
  • 1
    c++20 adds the ability to use constexpr with allocators https://www.cppstories.com/2021/constexpr-new-cpp20/ – Deev Jan 03 '22 at 21:35
  • 1
    That variable declaration is not valid. `std::string` is to be *usable* in constant evaluation. But it's not magically a literal type. – StoryTeller - Unslander Monica Jan 03 '22 at 21:37
  • @Artyer - Explicitly? You can't assume anything about its implementation. – StoryTeller - Unslander Monica Jan 03 '22 at 21:42
  • @Artyer - You are neglecting the constraint on its data members. Which the standard does not impose as literal types. So again, unless it's explicitly literal, you can't proclaim it as such. – StoryTeller - Unslander Monica Jan 04 '22 at 05:14
  • @Artyer - Although, if we can declare a variable of `std::string` type in a section that is constant evaluated, then it mustn't run afoul of https://timsong-cpp.github.io/cppwp/n4868/dcl.constexpr#3.5.3 - So, I guess it *would* be a literal type, if defined by a rather run-about way. – StoryTeller - Unslander Monica Jan 04 '22 at 13:07

1 Answers1

43

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Oh, is C++23 frozen already? I was rather hoping non-transient constexpr allocation would make it in :( – cigien Jan 03 '22 at 21:38
  • 1
    @cigien Not yet, but there haven't been any updates on it, so seems exceedingly unlikely at this point. – Barry Jan 03 '22 at 21:45
  • Thanks for the info. This makes me a bit sad. Now could you please show me a way to concatenate a few string literals and a few `constexpr` integers and store them in a compile-time evaluated `string`/`char*`?? I want to generate a simple message that won't be modified during run-time. I want this message to be generated by the compiler and stored in a read-only segment of the executable so that it does not get generated multiple times during run-time. – digito_evo Jan 03 '22 at 21:51
  • @cigien - The C++23 feature freeze is supposed to happen at [the next meeting in February](https://isocpp.org/std/meetings-and-participation/upcoming-meetings). With no active proposals by now, the odds are extremely low... – BoP Jan 03 '22 at 21:57
  • @digito_evo The final result just can't be something that has memory allocated. But it could be like a `const char[N]` - just have to pick the right `N`. – Barry Jan 03 '22 at 23:09
  • You might add that the string-value is too big for SSO on that implementation, which would be a way to avoid any allocation at all. – Deduplicator Jan 03 '22 at 23:17
  • @Barry I tried to concatenate the integers with a `std::string` and then maybe `strcpy` it to a `constexpr std::array` but that does not work and it says the `std::string` variable is a non-literal type. Probably have to wait until GCC 12.0 or 13.0... – digito_evo Jan 03 '22 at 23:23
  • 1
    @Deduplicator constexpr strings can't use SSO anyway, they would always allocate. – Barry Jan 03 '22 at 23:49
  • @digito_evo gcc 11.2 doesn't support constexpr string yet. – Barry Jan 03 '22 at 23:49
  • Why would constexpr strings always allocate? I really don't understand why they should... – Deduplicator Jan 04 '22 at 01:06
  • 1
    @Deduplicator It depends on whether the SSO implementation is based on reinterpret casting. If it is, have to allocate. libstdc++'s apparently doesn't, so it doesn't need to. – Barry Jan 04 '22 at 02:24
  • A bit of a side question, but if compilers can detect that allocated memory is not freed in constexpr expressions at compile time, then why can't the same be done elsewhere? Forgetting threads for a second, there are still plenty of opportunity for undefined behavior which happens when you use an object which has since been destroyed - and if compilers can detect this in constexpr, why not in the rest of the program? – Ant Jan 04 '22 at 08:25
  • 1
    @Ant they can detect it because `constexpr` involves actually running the code at compile time. You can do the same thing at runtime, using tools like valgrind, asan, ubsan etc. It's just not the default because these checks have a runtime cost. – Cubic Jan 04 '22 at 08:56
  • @Ant: The rest of the program can have branches conditioned on runtime data (user inputs, disk file contents, network I/O). As expansive as `constexpr` has become, it still doesn't encompass nearly all C++ programs. – Ben Voigt Jun 21 '22 at 22:30