Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't there a std::construct_at in C++17?

C++17 adds std::destroy_at, but there isn't any std::construct_at counterpart. Why is that? Couldn't it be implemented as simply as the following?

template <typename T, typename... Args> T* construct_at(void* addr, Args&&... args) {   return new (addr) T(std::forward<Args>(args)...); } 

Which would enable to avoid that not-entirely-natural placement new syntax:

auto ptr = construct_at<int>(buf, 1);  // instead of 'auto ptr = new (buf) int(1);' std::cout << *ptr; std::destroy_at(ptr); 
like image 292
Daniel Langr Avatar asked Oct 24 '18 10:10

Daniel Langr


People also ask

Why is allocator construct deprecated?

std::allocator 's construct and destroy are deprecated because they are useless: no good C++11 and later code should ever call them directly, and they add nothing over the default. Handling memory alignment should be the task of allocate , not construct .

What is the purpose of std :: any?

std::any. The class any describes a type-safe container for single values of any copy constructible type. 1) An object of class any stores an instance of any type that satisfies the constructor requirements or is empty, and this is referred to as the state of the class any object.


2 Answers

std::destroy_at provides two objective improvements over a direct destructor call:

  1. It reduces redundancy:

     T *ptr = new T;  //Insert 1000 lines of code here.  ptr->~T(); //What type was that again? 

    Sure, we'd all prefer to just wrap it in a unique_ptr and be done with it, but if that can't happen for some reason, putting T there is an element of redundancy. If we change the type to U, we now have to change the destructor call or things break. Using std::destroy_at(ptr) removes the need to change the same thing in two places.

    DRY is good.

  2. It makes this easy:

     auto ptr = allocates_an_object(...);  //Insert code here  ptr->~???; //What type is that again? 

    If we deduced the type of the pointer, then deleting it becomes kind of hard. You can't do ptr->~decltype(ptr)(); since the C++ parser doesn't work that way. Not only that, decltype deduces the type as a pointer, so you'd need to remove a pointer indirection from the deduced type. Leading you to:

     auto ptr = allocates_an_object(...);  //Insert code here  using delete_type = std::remove_pointer_t<decltype(ptr)>;  ptr->~delete_type(); 

    And who wants to type that?

By contrast, your hypothetical std::construct_at provides no objective improvements over placement new. You have to state the type you're creating in both cases. The parameters to the constructor have to be provided in both cases. The pointer to the memory has to be provided in both cases.

So there is no need being solved by your hypothetical std::construct_at.

And it is objectively less capable than placement new. You can do this:

auto ptr1 = new(mem1) T; auto ptr2 = new(mem2) T{}; 

These are different. In the first case, the object is default-initialized, which may leave it uninitialized. In the second case, the object is value-initialized.

Your hypothetical std::construct_at cannot allow you to pick which one you want. It can have code that performs default initialization if you provide no parameters, but it would then be unable to provide a version for value initialization. And it could value initialize with no parameters, but then you couldn't default initialize the object.


Note that C++20 added std::construct_at. But it did so for reasons other than consistency. They're there to support compile-time memory allocation and construction.

You can call the "replaceable" global new operators in a constant expression (so long as you haven't actually replaced it). But placement-new isn't a "replaceable" function, so you can't call it there.

Earlier versions of the proposal for constexpr allocation relied on std::allocator_traits<std::allocator<T>>::construct/destruct. They later moved to std::construct_at as the constexpr construction function, which construct would refer to.

So construct_at was added when objective improvements over placement-new could be provided.

like image 184
Nicol Bolas Avatar answered Sep 28 '22 17:09

Nicol Bolas


There is such a thing, but not named like you might expect:

  • uninitialized_copy copies a range of objects to an uninitialized area of memory

  • uninitialized_copy_n (C++11) copies a number of objects to an uninitialized area of memory (function template)

  • uninitialized_fill copies an object to an uninitialized area of memory, defined by a range (function template)

  • uninitialized_fill_n copies an object to an uninitialized area of memory, defined by a start and a count (function template)
  • uninitialized_move (C++17) moves a range of objects to an uninitialized area of memory (function template)
  • uninitialized_move_n (C++17) moves a number of objects to an uninitialized area of memory (function template)
  • uninitialized_default_construct (C++17) constructs objects by default-initialization in an uninitialized area of memory, defined by a range (function template)
  • uninitialized_default_construct_n (C++17) constructs objects by default-initialization in an uninitialized area of memory, defined by a start and a count (function template)
  • uninitialized_value_construct (C++17) constructs objects by value-initialization in an uninitialized area of memory, defined by a range (function template)
  • uninitialized_value_construct_n (C++17) constructs objects by value-initialization in an uninitialized area of memory, defined by a start and a count
like image 31
Marek R Avatar answered Sep 28 '22 18:09

Marek R