As constexpr std::string and constexpr std::vector have been accepted into C++20, how will these be used? The linked papers are very short on details. Do we need to specify special constexpr allocators, making compile-time strings/vectors incompatible with their normal equivalents?
Those two papers depend heavily on P0784, which discusses how allocations at compile-time will work.
Incomplete answer:
Only std::allocator will work.
All allocations are tracked, and must be deallocated before compilation is complete. This means that you can do manipulations at compile-time, but you can't initialize string and vector variables to be used at run-time. (Personally, I think there's a good chance that this restriction will be lifted in a future version of the standard - but that's just my opinion.)
Related
I'm learning the c++ STL and it came to my attention that while most functionalities supported by std::vector and std::array (contiguous storage) are marked with constexpr, that's not the case for std::deque and other non-contiguous storages.
So I spent some time doing some research, I found a proposal in 2019, Making std::deque constexpr, and std::deque still has not implemented constexpr for its methods.
My confusion is that std::array guarantees that its elements are stored on the stack; just like a normal C-style array, so it should be computed at compile time, but std::vector allocates memory on the heap so if it's evaluated at compile time, so is deque, right?
Thanks!
According to https://github.com/cplusplus/papers/issues/665 which keeps a log of the progression of the proposal through the standards committee process, there seem to have been some doubts whether a constexpr std::deque can be implemented without core language changes.
Unfortunately it doesn't say what the specific concern is. Probably some common implementation makes use of some language construct that specifically is not allowed in constant expressions or the implementation relies on some construct that is undefined behavior according to the standard. The latter is usually not a problem for the standard library, since it is not bound by the language rules and can make assumptions about the particular compiler's behavior. However in constant expressions core language undefined behavior is always a hard error and therefore such constructs might often not be usable in constant expression contexts without introducing magic compiler workarounds.
As mentioned in the linked github issue, there seem to also be some library facilities which need to have constexpr added to make this work as well.
Aside from such issues, generally, I don't think there is any reason to not make all containers and container adaptors constexpr-friendly now that std::allocator can be used in constant expressions. They probably just want to make sure that they can be properly implemented as constexpr first. My guess is that for the same reason only std::string and std::vector were done with C++20, since these are the simplest and most important allocator-aware containers to apply constexpr to. (std::array has been constexpr for longer since it doesn't require any dynamic allocations.)
Although, looking at the date of the last entry in the issue (as well as the accompanying issues for std::list, std::priority_queue, etc.) it seems to not have progressed in the last two years, maybe because the author of the proposal did not pursue it further, but I can't really tell.
In either case, when we say that std::vector (or other allocator-aware containers) are constexpr-friendly, this means something else than it does for e.g. std::array. You can declare a constexpr std::array variable and use it as you would use a const std::array variables, but you can't declare a constexpr std::vector variable (generally at all) and use it as you would a const std::vector.
What you can do however is use a std::vector variable in e.g. a constexpr function to do some calculations, as long as the variable is created after the evaluation of the constant expression starts and is destroyed before it ends. Currently this is not possible with e.g. a std::list which is not constexpr-friendly.
The reason for this is that the compiler will not actually allocate memory at compile-time for the container and then transfer it in some way into a runtime allocation (whether static or dynamic). Instead dynamic allocations at compile-time are separate from runtime ones and must be deallocated before the constant expression evaluation in which they were allocated ends.
As constexpr std::string and constexpr std::vector have been accepted into C++20, how will these be used? The linked papers are very short on details. Do we need to specify special constexpr allocators, making compile-time strings/vectors incompatible with their normal equivalents?
Those two papers depend heavily on P0784, which discusses how allocations at compile-time will work.
Incomplete answer:
Only std::allocator will work.
All allocations are tracked, and must be deallocated before compilation is complete. This means that you can do manipulations at compile-time, but you can't initialize string and vector variables to be used at run-time. (Personally, I think there's a good chance that this restriction will be lifted in a future version of the standard - but that's just my opinion.)
I understand that copying arbitrary chunks of memory is not always possible to do at compile time but since we are getting constexpr containers, virtual methods and also algorithms, why not memcpy too? It is too a kind of algorithm.
Furthemore,
C++20 std::bit_cast seems a lot like std::memcpy workaround reinterpret_cast but it is constexpr.
std::copy using iterators is marked as constexpr for C++20, so copying is somehow possible for types.
The usage would be to either copy or just "reinterpret" variables/arrays in constexpr functions, the former is not solved by std::bit_cast AFAIK. In particular, the question and my answer would like to use it.
Is there any particular reason for why std::bit_cast can be constexpr but std::memcpy cannot?
Does it have to do with memcpy using void pointers instead of typed references?
Not actually having to copy anything?
C backwards compatibility?
Maybe because there is no support for a "pointer to constexpr memory"? But the same applies to the reference parameter in std::bit_cast and iterators in std::copy.
Relevant answer to C++20 bit_cast vs reinterpret_cast briefly cites from somewhere:
Furthermore, it is currently impossible to implement a constexpr
bit-cast function, as memcpy itself isn’t constexpr. Marking the
proposed function as constexpr doesn’t require or prevent memcpy from
becoming constexpr, but requires compiler support. This leaves
implementations free to use their own internal solution (e.g. LLVM has
a bitcast opcode).
But it does not go into detail of not making it constexpr too.
Note, that I do not ask for why std::bit_cast exists. I like it, it provides a clear intention instead of std::memcpy workaround.
The C++ object model in runtime code is generally treated somewhat loosely. It has fairly strict rules, but there are a bunch of backdoors that are either allowed or declared UB. The latter means that you can still write code to do it, but C++ guarantees nothing about the behavior of that code.
Within constant evaluation (aka: compile-time execution of code), this is not the case. The restrictions on constexpr are specifically intended to allow the object model to be a real thing that you must follow, with no viable backdoors. And even the ones that it occasionally permits are explicitly required to be ill-formed and produce a compile-error, rather than being silent UB.
Basically at runtime, you get to treat memory as just bytes of storage. At compile-time, you can't; you're not allowed to. Even with dynamic allocation in constexpr code added in C++20, you don't get to play a lot of the games you usually get to play with that sort of thing.
memcpy deals in bytes of storage, copying them back and forth with no idea what they mean. bit_cast knows both the source and destination objects, and it will not allow you to do it unless the source and destination objects are appropriate for bit_casting (ie: trivially-copyable).
bit_cast also has very specific restrictions on the content of both such objects if you want it to work at compile-time. In particular, you can't bit_cast pointers or any objects containing pointers of any kind. Or references.
This is because pointers at compile-time are not just addresses. In order to catch UB, a compile-time pointer has to know the true dynamic type of the object it points to. So pointer conversions that just convert the address aren't allowed at compile-time.
That's more a comment then an answer as I'm only citing what is written in P0202R0: Add Constexpr Modifiers to Functions in and Headers, but I write it here as is does not fit the comments:
B. std::memmove and std::memcpy must have constexpr additions
std::memmove and std::memcpy accept void* and const void* parameters. This makes them impossible to implement in pure C++ as constexpr, because constant expressions can not evaluate a conversion from type cv void * to a pointer-to-object type according to [expr.const].
However those functions are not only popular, but also are widely used across Standard Library to gain better performance. Not making them constexpr will force standard Library developer to have compiler intrinsics for them anyway. This is a hard step that must be done.
The related section of [expr.const]:
8.6 Constant expressions [expr.const]
[…]An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.8.1), would evaluate one of the following expressions:
[…]
(2.13) — a conversion from type cv void* to a pointer-to-object type;
I am just realizing that some functions of the Dynamic memory management standard library have been deprecated in C++17. An example is get_temporary_buffer:
template< class T >
std::pair< T*, std::ptrdiff_t > get_temporary_buffer( std::ptrdiff_t count );
Can somebody explain why? Can I expect there to be an alternative in C++20?
According to the proposal that deprecates it:
This API would be considered an incomplete thought were it proposed today. As a functional API it lacks exception safety if the function allocating the buffer leaks, yet we offer no RAII-like wrappers to promote safe use.
It has been suggested that all current implementation of this API actually do not perform a more efficient allocation than the regular new operator, and, if that is genuinely the case, we should seriously consider deprecating this facility. Otherwise, we should probably complete the design with an appropriate guard/wrapper class, and encourage vendors to deliver on missed optimization opportunities.
In short, just use new/delete. Or your own temporary memory allocator; whichever works best for your needs.
In an answer to this SO question:
What is the equivalent of boost::variant in the C++ standard library?
it is mentioned that boost::variant and std::variant differ somewhat.
What are the differences, as far as someone using these classes is concerned?
What motivation did the committee express to adopt std::variant with these differences?
What should I watch out for when coding with either of these, to maintain maximum compatibility with switching to the other one?
(the motivation is using boost::variant in pre-C++17 code)
Assignment/emplacement behavior:
boost::variant may allocate memory when performing assignment into a live variant. There are a number of rules that govern when this can happen, so whether a boost::variant will allocate memory depends on the Ts it is instantiated with.
std::variant will never dynamically allocate memory. However, as a concession to the complex rules of C++ objects, if an assignment/emplacement throws, then the variant may enter the "valueless_by_exception" state. In this state, the variant cannot be visited, nor will any of the other functions for accessing a specific member work.
You can only enter this state if assignment/emplacement throws.
Boost.Variant includes recursive_variant, which allows a variant to contain itself. They're essentially special wrappers around a pointer to a boost::variant, but they are tied into the visitation machinery.
std::variant has no such helper type.
std::variant offers more use of post-C++11 features. For example:
It forwards the noexcept status of the special member functions of its constituent types.
It has variadic template-based in-place constructors and emplacement functions.
Defect resolutions applied to C++17 may mean that it will also forward trivial copyability of its types. That is, if all of the types are trivially copyable, then so too will variant<Ts>.
It seems the main point of contention regarding the design of a variant class has been what should happen when an assignment to the variant, which should upon completion destory the old value, throws an exception:
variant<std::string, MyClassWithThrowingDefaultCtor> v = "ABC";
v = MyClassWithThrowingDefaultCtor();
The options seem to be:
Prevent this by restricting the possible representable types to nothrow-move-constructible ones.
Keep the old value - but this requires double-buffers.
Construct the new value on the heap, store a pointer to it in the variant (so the variant itself is not garbled even on exception). This is, apparently, what boost::variant does.
Have a 'disengaged' state with no value for each variant, and go to that state on such failures.
Undefined behavior
Make the variant throw when trying to read its value after something like that happens
and if I'm not mistaken, the latter is what's been accepted.
This is summarized from the ISO C++ blog post by Axel Naumann from Nov 2015.
std::variant differs slightly from the boost::variant
std::variant is declared in the header file rather than in <boost.variant.hpp>
std::variant never ever allocates memory
std::variant is usable with constexpr
Instead of writing boost::get(&variable), you have to write std::get_if(&variable) for std::variant
std::variant can not recursively hold itself and misses some other advanced techniques
std::variant can in-place construct objects
std::variant has index() instead of which()