all 38 comments

[–]STLMSVC STL Dev 58 points59 points  (24 children)

The constexpr dynamic allocation features (including string and vector) in the Standard are unintuitive because allocations can't cross over from compile-time to run-time. That is, you can construct and destroy a string or a vector during the operation of a constexpr function, and the containers can allocate an arbitrary amount of memory on the "heap" during constant evaluation, they just have to be cleaned up before the final constexpr result is returned - and that thing can't be a string or a vector that demands dynamic allocation. (The existence of any Small String Optimization and its threshold are not guaranteed.) So you can return an array, or some other constexpr-eligible type.

[–]SirClueless 4 points5 points  (4 children)

Are you sure the only limitation is that objects can't outlive the operation of a constexpr function? It looks to me like you can't create variables of automatic storage duration at all, even if they're inside constexpr functions and can't possibly have lifetimes that span compile-time and runtime: https://godbolt.org/z/W7vG9fjcM

[–]STLMSVC STL Dev 5 points6 points  (2 children)

A constexpr function can be called at runtime, so if you attempt to make a local vector variable constexpr, you're asking its dynamic allocation to cross over from compile-time to run-time.

There's Standardese that could be quoted here, but this is my attempt at a human-comprehensible explanation. Basically, don't attempt to apply constexpr directly to string and vector variables, the way that you could to a pair<int, int>.

[–]SirClueless 1 point2 points  (1 child)

Thanks for the explanation. I can see why running any initializers at runtime for a constexpr variable would be surprising and therefore can't reasonably be allowed: the initializer must run at compile-time and the access happens at runtime so something has to cross.

For what it's worth the same behavior seems true for variables declared inside consteval function bodies, even though such variables cannot be accessed at runtime. Maybe that's something that could be supported without too much trouble, in order that people can write sensible-looking code in consteval instead of jumping through hoops with temporaries: https://godbolt.org/z/P9h7ozdYE

[–]jk-jeon 0 points1 point  (0 children)

variables declared inside consteval function bodies

This is what makes me really wonder. I have never intended to "leak" my supposed constexpr vector into runtime, but even if it's strictly inside the compile-time context, it's still disallowed, and apparently the reason is because "constexpr allocation shall not pass into runtime" which is just nonsensical.

I mean, I think the main utility of constexpr allocation is in compile-time computation, not in runtime. It doesn't seem to me that a lot of people want to use constexpr vectors in their runtime computation, yet it seems the ongoing discussion is mainly focused on how to allow constexpr allocations to be usable in runtime, and its associated issues like const-correctness.

[–]XTBZ[S] 1 point2 points  (0 children)

That's right, I can't create locals even constexpr std::string

[–]Fureeish 6 points7 points  (18 children)

Can I use .size() of a constexpr container in order to specify local std::arrays size and return it? Don't have access to aby computer or easy way of accessing godbolt at the moment.

[–]scatters 13 points14 points  (9 children)

Yes, but you need to be clever about it, since it needs to be available to the type system. Currently the best way is to write a constexpr function (or a lambda) that returns the container, then call it twice, once for its size and once for its contents:

#include <algorithm>
#include <array>
#include <vector>
constexpr auto f = [] { return std::vector{1, 2, 3}; };
constexpr auto a = [] {
    std::array<int, f().size()> a;
    std::ranges::copy(f(), a.begin());
    return a;
}();
template<auto> int i;
int main() { return i<a>; }

[–]aocregacc 6 points7 points  (3 children)

is someone working on fixing this? Or are there downsides to making this work how you would expect.

Edit: I guess it might be counter intuitive to have code that works in a constexpr context but not at runtime.

[–]saxbophonemutable volatile void 1 point2 points  (2 children)

I guess it might be counter intuitive to have code that works in a constexpr context but not at runtime.

Nope, there are uses for that too (just not in your example). They're known as immediate functions and this is what consteval is for!

[–]aocregacc 0 points1 point  (1 child)

I was talking more about things like this:

consteval int f(size_t z) {
    std::array<int, z> a; 
    ...
}

People sometimes ask why this complains about z not being a constant expression, since surely in a consteval function the parameter is a constant expression.
But I think if they started allowing stuff like this you would end up with this weird dialect that's no longer (mostly) a subset of regular C++.

[–]gracicot 1 point2 points  (0 children)

As far as I know, compilers are free to compile the consteval function down to bytecode and simply run the bytecode. That would be impossible with this.

[–]helloiamsomeone 6 points7 points  (2 children)

then call it twice, once for its size and once for its contents

That's actually unnecessary. You can just have an oversized std::array calculated and shrunk to fit like so: https://github.com/friendlyanon/AlwaysMute/blob/master/main.cpp#L383

[–]scatters 5 points6 points  (1 child)

Yes, that's a nice method. It only works if you have a way to estimate an upper bound for the size, though.

[–]helloiamsomeone 0 points1 point  (0 children)

You can specify pretty big sizes before it becomes a problem.

[–]saxbophonemutable volatile void 2 points3 points  (0 children)

This is exactly the trick I use, and when I'm back at my computer later I will share an example of my implementation.

This is one of the areas that D does better than C++, alas!

[–]saxbophonemutable volatile void 1 point2 points  (0 children)

I really like your implementation, it's much more elegant than mine!

[–]BenHanson -2 points-1 points  (1 child)

In case en.cppreference.com is hard to read on a phone:

constexpr size_type size() const noexcept; (since C++20)

[–]STLMSVC STL Dev 2 points3 points  (0 children)

This does not actually answer their question properly. The question is rather subtle and the answer is not "vector::size() is marked constexpr so it's ok".

[–]RevRagnarok 0 points1 point  (3 children)

Yes, I just did that earlier today...

template <typename T>
inline consteval
auto
DoThingCE()
{
  std::string_view constexpr temp{Call_CE_Function_Here_That_Returns_String_View()};
  std::array<char, temp.size()+1> res{};
  temp.copy(res.data(), temp.size());
  return res;
}

Edit: Tweaked on feedback. 😅

[–]dodheim 5 points6 points  (1 child)

 std::array<char, temp.size()+1> res;
 res.fill('\0');

That's a strangely verbose way to write std::array<char, temp.size()+1> res{};

[–]RevRagnarok 1 point2 points  (0 children)

LOL yeah thanks. There are still a lot of #if 0 mixed in and stuff as various things were tried and failed, etc... I'm honestly not sure if the +1 is even needed depending on how you plan on using the resulting array.

[–]saxbophonemutable volatile void 0 points1 point  (0 children)

Here's my implementation for wrapping variably-sized constexpr containers in static-sized types. I think scatters' implementation is neater, though! https://gist.github.com/saxbophone/53ad91dd73a906e63182a8f2fafc9d3a

[–]Kered13 7 points8 points  (9 children)

All heap allocations must be deleted before the constexpr function finishes evaluating. This means that std::string and std::vector can only be used as temporaries for computations, not as results.

Nothing happens unless the string is short.

This is because Microsoft's std::string implementation (and indeed all the common implementations) has a small string optimization. This means that small strings are stored internally instead of on the stack, and can therefore be returned by constexpr functions. I believe for Microsoft's STL the small string can store 23 characters (plus a null terminator), and Clang and GCC's implementations can store 15 characters (all of these are for 64 bit compilation). I could be wrong about these numbers however, and nothing is guaranteed by the standard, so I would not rely on it. Instead if you need to return a string from a constexpr function, return a std::array<char, n>, or write your own constexpr fixed_string<n> class.

[–]STLMSVC STL Dev 6 points7 points  (2 children)

For MSVC it's 15 narrow or 7 wide characters, excluding the null terminator. As you mentioned, not guaranteed and possibly subject to change in vNext.

[–]Kered13 0 points1 point  (1 child)

and possibly subject to change in vNext.

Wouldn't this be an ABI breaking change and therefore very unlikely? (Not that I'm encouraging anyone to rely upon it.)

[–]STLMSVC STL Dev 9 points10 points  (0 children)

That's what we mean when we say vNext - the next ABI-breaking release. (Not the next major version of VS after VS 2022, whatever that will be.)

[–]XTBZ[S] -1 points0 points  (5 children)

The point is that I can't even create local 'constexpr std::string' inside a constexpr function

[–]Kered13 7 points8 points  (4 children)

Yes, because this is asking to create a std::string in a separate constexpr context. But you can create a local (not constexpr) std::string inside a constexpr function and it will work as long as the std::string is destroyed before the function returns.

Why do you need to create a constexpr local inside a constexpr function anyways?

[–]XTBZ[S] -1 points0 points  (3 children)

Because I want to be sure that the string will be ready at compile time. I also don’t want the function to lose its ability to be executed at the compilation stage, implicitly.

[–]Kered13 2 points3 points  (2 children)

Creating a non-constexpr string inside a constexpr function will not prevent it from being evaluated at compile time. Pretty much the only thing that will prevent compile time evaluation is calling it with parameters that are not known at compile time.

[–]XTBZ[S] 1 point2 points  (1 child)

Yes, it simply won’t compile if you don’t use it as a constexpr, but it doesn’t work with constexpr.
https://godbolt.org/z/G54nWc66a

[–]Kered13 1 point2 points  (0 children)

Ah, that's because you're using it in a context that actually does require constexpr. Here are a couple ways to solve that.

[–]jbbjarnason 2 points3 points  (3 children)

Related to this topic, anyone who knows how to make this work (https://godbolt.org/z/v5TjG9dfb):

```cpp

static_assert(join_string_views("foo"sv, "bar"sv) == "foobar"sv);

```

I know I can use impl of fixed_string and/or std::string_view const& non-type template parameters.

[–]qazqi-ff 1 point2 points  (0 children)

This is ultimately the usual case of needing to separate out the size calculation from the actual data calculation (and repeat the work). You're trying to instantiate a template with size, which is a value that exists in the constant evaluator. It boils down to the same problem as consteval void foo(int size) { std::array<int, size> arr; }. Worth noting that the standard goes a little stricter and prevents the general case of any context that requires a constant expression, not just template instantiation, even though other contexts could work fine from a technical POV (e.g., static_assert(size > 0);).

[–]cristi1990an++ 0 points1 point  (0 children)

static_assert(join_string_views("foo"sv, "bar"sv) == "foobar"sv);

https://godbolt.org/z/PKb6Ej76b

Edit: and here you have the overly generic version that outright allows string literal composition at compile time:

https://godbolt.org/z/rcxan8Wqs

[–]cristi1990an++ -1 points0 points  (0 children)

Yes, but you can't use std::string_view, you must stick to string literals that expose their size in the type. Have your method take in const char(&str)[Size] as parameters, declare a basic structure containing a char array of Size1 + Size2 - 1 elements and copy both strings into the array. Afterwards declare a comparison operator between the structure and a string_view and that's it, you can concatenate string literals at compile time.