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

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);

std::destroy_at provides two objective improvements over a direct destructor call:
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.
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.

std::construct_at has been added to C++20. The paper that did so is More constexpr containers. Presumably, this was not seen to have enough advantages over placement new in C++17, but C++20 changes things.
The purpose of the proposal that added this feature is to support constexpr memory allocations, including std::vector. This requires the ability to construct objects into allocated storage. However, just plain placement new deals in terms of void *, not T *. constexpr evaluation currently has no ability to access the raw storage, and the committee wants to keep it that way. The library function std::construct_at adds a typed interface constexpr T * construct_at(T *, Args && ...).
This also has the advantage of not requiring the user to specify the type being constructed; it is deduced from the type of the pointer. The syntax to correctly call placement new is kind of horrendous and counter-intuitive. Compare std::construct_at(ptr, args...) with ::new(static_cast<void *>(ptr)) std::decay_t<decltype(*ptr)>(args...).

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

There is std::allocator_traits::construct. There used to be one more in std::allocator, but that got removed, rationale in standards committee paper D0174R0.

I think there should be a standard construct-function.
In fact libc++ has one as an implementation detail in the file stl_construct.h.
namespace std{
...
template<typename _T1, typename... _Args>
inline void
_Construct(_T1* __p, _Args&&... __args)
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
...
}
I think is it something useful to have because it allows to make "placement new" a friend.
This is a great customization point for a move-only type that need uninitialized_copy into the default heap (from an std::initializer_list element for example.)
I have my own container library that reimplements a detail::uninitialized_copy (of a range) to use a custom detail::construct:
namespace detail{
template<typename T, typename... As>
inline void construct(T* p, As&&... as){
::new(static_cast<void*>(p)) T(std::forward<As>(as)...);
}
}
Which is declared a friend of a move-only class to allow copy only in the context of placement new.
template<class T>
class my_move_only_class{
my_move_only_class(my_move_only_class const&) = default;
friend template<class TT, class...As> friend void detail::construct(TT*, As&&...);
public:
my_move_only_class(my_move_only_class&&) = default;
...
};

construct does not seem to provide any syntactic sugar. Moreover it is less efficient than a placement new. Binding to reference arguments cause temporary materialization and extra move/copy construction:
struct heavy{
unsigned char[4096];
heavy(const heavy&);
};
heavy make_heavy(); // Return a pr-value
auto loc = ::operator new(sizeof(heavy));
// Equivalently: unsigned char loc[sizeof(heavy)];
auto p = construct<heavy>(loc,make_heavy()); // The pr-value returned by
// make_heavy is bound to the second argument,
// and then this arugment is copied in the body of construct.
auto p2 = new(loc) auto(make_heavy()); // Heavy is directly constructed at loc
//... and this is simpler to write!
Unfortunately there isn't any way to avoid these extra copy/move construction when calling a function. Forwarding is almost perfect.
On the other hand, construct_at in the library could complete the standard library vocabulary.

Related

Does make_shared work for (nested) POD types? [duplicate]

To be specific: direct-list-initialization (cppreference.com (3)).
Both std::make_shared and uniform initialization features were introduced in C++11. So we can use aggregate initialization when allocating objects on heap: new Foo{1, "2", 3.0f}. This is a nice way to directly initialize objects that have no constructors, such as aggregates, pods, etc.
A real-life scenarios, such as declaring casual structures within a function, to efficiently supply set of arguments to a lambda became very common, in my experience:
void foo()
{
struct LambdaArgs
{
std::string arg1;
std::string arg2;
std::string arg3;
};
auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});
auto lambda = [args] {
/// ...
};
/// Use lambda
/// ...
}
Here auto args = std::make_shared<LambdaArgs>("1", "2", "3"); whould be nice but isn't going to work, because std::make_shared is usually implemented as:
template<typename T, typename... Args>
std::shared_ptr<T> make_shared(Args && ...args)
{
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
So we're stuck with the auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor. And the workaround is not only unaesthetic but also less efficient.
Is this another oversight or are there some reasons that defend this choice. Specifically, what pitfalls can be in the list initialization solution? std::make_unique was introduced later, in C++14, why does it too follow same pattern?
Specifically, what pitfalls can be in the list initialization solution?
All of the typical pitfalls of using list-initialization.
For example, the hiding of non-initializer_list constructors. What does make_shared<vector<int>>(5, 2) do? If your answer is "constructs an array of 5 ints", that's absolute correct... so long as make_shared isn't using list-initialization. Because that changes the moment you do.
Note that suddenly changing this would break existing code, since right now all of the indirect initialization functions use constructor syntax. So you can't just change it willy-nilly and expect the world to keep working.
Plus one more unique to this case: the narrowing issue:
struct Agg
{
char c;
int i;
};
You can do Agg a{5, 1020}; to initialize this aggregate. But you could never do make_shared<Agg>(5, 1020). Why? Because the compiler can guarantee that the literal 5can be converted to a char with no loss of data. However, when you use indirect initialization like this, the literal 5 is template-deduced as int. And the compiler cannot guarantee that any int can be converted to a char with no loss of data. This is called a "narrowing conversion" and is expressly forbidden in list initialization.
You would need to explicitly convert that 5 to a char.
The standard library has an issue on this: LWG 2089. Though technically this issue talks about allocator::construct, it should equally apply to all indirect initialization functions like make_X and C++17's in-place constructors for any/optional/variant.
why does it too follow same pattern?
It follows the same pattern because having two different functions that look almost identical that have radically and unexpectedly different behaviors would not be a good thing.
Note that C++20 resolves the aggregate part of this issue at least by making constructor-style syntax invoke aggregate initialization if the initializers would have been ill-formed for regular direct initialization. So if T is some aggregate type (with no user-declared constructors), and T(args) wouldn't invoke a copy/move constructor (the only constructors that take arguments which a type with no user-declared constructors could have), then the arguments will instead be used to attempt to aggregate initialize the structure.
Since allocator::construct and other forms of forwarded initialization default to direct-initialization, this will let you initialize aggregates through forwarded initialization.
You still can't do other list-initialization stuff without explicitly using an initializer_list at the call site. But that's probably for the best.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor.
No, the problem does not persist. The main problem make_shared is solving is a potential for a memory leak between the object is allocated and the ownership is taken by the smart pointer. It is also capable of removing one extra allocation for control block.
Yes, it is inconvenient to not be able to use a direct initialization, but this was never the declared goal of make_shared.

Is there a reason why std::make_shared/std::make_unique don't use list initialization?

To be specific: direct-list-initialization (cppreference.com (3)).
Both std::make_shared and uniform initialization features were introduced in C++11. So we can use aggregate initialization when allocating objects on heap: new Foo{1, "2", 3.0f}. This is a nice way to directly initialize objects that have no constructors, such as aggregates, pods, etc.
A real-life scenarios, such as declaring casual structures within a function, to efficiently supply set of arguments to a lambda became very common, in my experience:
void foo()
{
struct LambdaArgs
{
std::string arg1;
std::string arg2;
std::string arg3;
};
auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});
auto lambda = [args] {
/// ...
};
/// Use lambda
/// ...
}
Here auto args = std::make_shared<LambdaArgs>("1", "2", "3"); whould be nice but isn't going to work, because std::make_shared is usually implemented as:
template<typename T, typename... Args>
std::shared_ptr<T> make_shared(Args && ...args)
{
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
So we're stuck with the auto args = std::make_shared<LambdaArgs>(LambdaArgs{"1", "2", "3"});.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor. And the workaround is not only unaesthetic but also less efficient.
Is this another oversight or are there some reasons that defend this choice. Specifically, what pitfalls can be in the list initialization solution? std::make_unique was introduced later, in C++14, why does it too follow same pattern?
Specifically, what pitfalls can be in the list initialization solution?
All of the typical pitfalls of using list-initialization.
For example, the hiding of non-initializer_list constructors. What does make_shared<vector<int>>(5, 2) do? If your answer is "constructs an array of 5 ints", that's absolute correct... so long as make_shared isn't using list-initialization. Because that changes the moment you do.
Note that suddenly changing this would break existing code, since right now all of the indirect initialization functions use constructor syntax. So you can't just change it willy-nilly and expect the world to keep working.
Plus one more unique to this case: the narrowing issue:
struct Agg
{
char c;
int i;
};
You can do Agg a{5, 1020}; to initialize this aggregate. But you could never do make_shared<Agg>(5, 1020). Why? Because the compiler can guarantee that the literal 5can be converted to a char with no loss of data. However, when you use indirect initialization like this, the literal 5 is template-deduced as int. And the compiler cannot guarantee that any int can be converted to a char with no loss of data. This is called a "narrowing conversion" and is expressly forbidden in list initialization.
You would need to explicitly convert that 5 to a char.
The standard library has an issue on this: LWG 2089. Though technically this issue talks about allocator::construct, it should equally apply to all indirect initialization functions like make_X and C++17's in-place constructors for any/optional/variant.
why does it too follow same pattern?
It follows the same pattern because having two different functions that look almost identical that have radically and unexpectedly different behaviors would not be a good thing.
Note that C++20 resolves the aggregate part of this issue at least by making constructor-style syntax invoke aggregate initialization if the initializers would have been ill-formed for regular direct initialization. So if T is some aggregate type (with no user-declared constructors), and T(args) wouldn't invoke a copy/move constructor (the only constructors that take arguments which a type with no user-declared constructors could have), then the arguments will instead be used to attempt to aggregate initialize the structure.
Since allocator::construct and other forms of forwarded initialization default to direct-initialization, this will let you initialize aggregates through forwarded initialization.
You still can't do other list-initialization stuff without explicitly using an initializer_list at the call site. But that's probably for the best.
The problem that was supposed to be solved with std::make_shared still persists for object without constructor.
No, the problem does not persist. The main problem make_shared is solving is a potential for a memory leak between the object is allocated and the ownership is taken by the smart pointer. It is also capable of removing one extra allocation for control block.
Yes, it is inconvenient to not be able to use a direct initialization, but this was never the declared goal of make_shared.

std::experimental::optional inside constexpr function

I would like to use the optional idiom inside my constexpr function to easily clarify if the variable is set or not.
What I have tried with std::experimental::optional:
constexpr bool call()
{
std::experimental::optional<bool> r;
r = true; // Error
// Similar error with:
// r = std::experimental::optional<bool>(true);
if (!r)
{
return false;
}
return *r;
}
I get the error: call to non-constexpr function - so the assignment is not possible, because this operation cannot be constexpr (Example).
But if I implement my own (very ugly, just for example) optional class, it works, because I don´t implement the assignment operator/constructor explicit.
template<typename T>
struct optional
{
bool m_Set;
T m_Data;
constexpr optional() :
m_Set(false), m_Data{}
{
}
constexpr optional(T p_Data) :
m_Set(true), m_Data(p_Data)
{
}
explicit constexpr operator bool()
{
return m_Set;
}
constexpr T operator *()
{
return m_Data;
}
};
How could I use std::..::optional in the same context with assignment inside constexpr functions?
Basically, you can't. The problem with your simple implementation is that it requires T be default-constructible - if this is not the case, this won't work.
To get around this, most implementation use either a union or some (suitably aligned) storage that can hold a T. If you are passed a T in the constructor, then all well and good, you can initialize this directly (hence it will be constexpr). However, the tradeoff here is that when calling operator=, copying the value across may require a placement-new call, which cannot be constexpr.
For example, from LLVM:
template <class _Up,
class = typename enable_if
<
is_same<typename remove_reference<_Up>::type, value_type>::value &&
is_constructible<value_type, _Up>::value &&
is_assignable<value_type&, _Up>::value
>::type
>
_LIBCPP_INLINE_VISIBILITY
optional&
operator=(_Up&& __v)
{
if (this->__engaged_)
this->__val_ = _VSTD::forward<_Up>(__v);
else
{
// Problem line is below - not engaged -> need to call
// placement new with the value passed in.
::new(_VSTD::addressof(this->__val_)) value_type(_VSTD::forward<_Up>(__v));
this->__engaged_ = true;
}
return *this;
}
As for why placement new is not constexpr, see here.
This is not possible, as explained in n3527:
Making optional a literal type
We propose that optional<T> be a literal type for trivially
destructible T's.
constexpr optional<int> oi{5};
static_assert(oi, ""); // ok
static_assert(oi != nullopt, ""); // ok
static_assert(oi == oi, ""); // ok
int array[*oi]; // ok: array of size 5
Making optional<T> a literal-type in general is impossible: the
destructor cannot be trivial because it has to execute an operation
that can be conceptually described as:
~optional() {
if (is_engaged()) destroy_contained_value();
}
It is still possible to make the destructor trivial for T's which
provide a trivial destructor themselves, and we know an efficient
implementation of such optional<T> with compile-time interface —
except for copy constructor and move constructor — is possible.
Therefore we propose that for trivially destructible T's all
optional<T>'s constructors, except for move and copy constructors,
as well as observer functions are constexpr. The sketch of reference
implementation is provided in this proposal.
In other words, it's not possible to assign a value to r even if you mark it as constexpr. You must initialize it in the same line.
How could I use std::..::optional in the same context with assignment
inside constexpr functions?
std::optional is meant to hold a value that may or may not be present. The problem with std::optional's assignment is that it must destroy the old state (call the destructor of the contained object) if any. And you cannot have a constexpr destructor.
Of cause, Trivial and integral types shouldn't have a problem, but I presume the generalization was to keep things sane. However, Assignment could have been made constexpr for trivial types. Hopefully, it will be corrected. Before then, you can role out yours. :-)
Even std::optional's constructor that you think is constexpr, is actually selectively constexpr (depending on whether the selected object constructor is). Its proposal can be found here
Unfortunately, constexpr support in std::optional is somewhat rudimentary; the constexpr-enabled member functions are just the (empty and engaged) constructors, the destructor and some observers, so you cannot alter the engaged state of an optional.
This is because there would be no way to implement assignment for non-trivially copyable types without using placement new and in-place destruction of the contained object, which is illegal within constexpr context. The same currently holds for copy and move constructors, although that may change with guaranteed copy elision, but in any case the standard marks those special member functions as non-constexpr, so you cannot use them in constexpr context.
The fix would be to make the assignment operator conditionally constexpr dependent on whether the contained type is trivial (std::is_trivial_v<T>).
There is some discussion of this issue at the reference implementation; although it's probably too late to get constexpr assignment for trivial optionals into the next version of the Standard, there's nothing preventing you writing your own (e.g. by copying and fixing the reference implementation).

Using placement new on nullptr in decltype() or operator noexcept() context

Is it allowed by the Standard to write decltype(::new (nullptr) T(std::declval< Args >()...)) or noexcept(::new (nullptr) T(std::declval< Args >()...))? Particularly interested placement new on nullptr correctness. Considering following code:
#include <type_traits>
#include <utility>
struct S { S(int) { ; } ~S() = delete; };
struct A
{
template< typename T,
bool is_noexcept = noexcept(::new (nullptr) S(std::declval< T >())) >
A(T && x) noexcept(is_noexcept)
: s(new S(std::forward< T >(x)))
{ ; }
S * s;
};
static_assert(std::is_constructible< A, int >{});
static_assert(!std::is_constructible< A, void * >{});
Disabler typename = decltype(S(std::declval< T >())) would need presence of destructor, but placement new not.
Nevertheless unevaluated context of decltype and operator noexcept I wondering about conformance to the Standard. Because compiler may prove 100% incorectness of the tested expression.
In all published versions of the standard it is OK to pass a null pointer to placement new, and the compiler is required to handle that case, so the code is OK if it calls ::operator new(size_t, void*) with a null pointer.
However, a class could have overloaded operator new(size_t, nullptr_t) in which case your expression would use that, and so you can't know exactly what would happen (e.g. it might be deleted, or it might be noexcept(false)).
In the working draft for C++17 it is undefined behaviour if a reserved placement allocation function returns null (this was changed following discussion arising from a question I asked here and on the committee lists). It's not clear whether that means that it is actually called and returns null, or whether even your usage would be undefined.
I would prefer not to risk it, and would use something that more clearly expresses the intention. Since what you are trying to do is test whether construction (but not destruction) can throw, by using a new expression that won't throw, say exactly that, and use new(std::nothrow), or use an expression that uses a void* that is not provably a null pointer: new (std::declval<void*>()).
This avoids any confusion due to using nullptr when the property you are testing is unrelated to nullptr. Involving nullptr just complicates things, by making the reader wonder if the use of nullptr is significant, or if you're just being lazy and using that as a shorthand for "some void* pointer value".

weak_ptr's weird copy constructors

the following are 2 of weak_ptr's constructors:
http://msdn.microsoft.com/en-us/library/bb982126.aspx
weak_ptr(const weak_ptr&);
template<class Other>
weak_ptr(const weak_ptr<Other>&);
actual code (from memory):
weak_ptr(const weak_ptr& _Other)
{ // construct weak_ptr object for resource pointed to by _Other
this->_Resetw(_Other);
}
template<class _Ty2>
weak_ptr(const weak_ptr<_Ty2>& _Other,
typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
void *>::type * = 0)
{ // construct weak_ptr object for resource pointed to by _Other
this->_Resetw(_Other);
}
Q1: Why is the top copy constructor even there? It looks like the bottom one accounts for every case (including the top one). Does it even get called? and if they didn't include it would the bottom one take it's place?
Q2: What's going on with the second argument of the bottom (templated) constructor. I think I understand the SFINAE aspect, but I don't understand why there is an extra * after ::type
Q1) If you don't write a copy constructor, the compiler will generate one for you, which wouldn't be what you want. Templated conversion constructors don't count.
Q2) Remember that shared_ptr<T> is like a T*, convertibility must be checked at the level of pointers. If T* is convertible to U* then you should be able to assign one to the other. Think of pointers-to-base. [Sorry, that wasn't what you asked.] The final argument type just needs to exist, but also we don't want to have to specify the argument itself. A universal way of making up a type for which we can also provide a default argument is a pointer. In short, we need to make the function depend on a type that may or may not exist, but without actually requiring the user to know about this.
Re Q1: a templated constructor is never a "copy constructor", even if it manages to copy. if there is no user-defined "copy constructor", then the compiler will generate one as needed.
Re Q2: the second argument, a pointer defaulted to 0, is just to have a place to put the enable_if. you can find more about that (if i recall correctly) in the Boost documentation.
Cheers & hth.,