Tomilov Anatoliy
Tomilov Anatoliy

Reputation: 16731

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.

Upvotes: 3

Views: 762

Answers (1)

Jonathan Wakely
Jonathan Wakely

Reputation: 171403

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".

Upvotes: 5

Related Questions