Date: Tue, 13 Aug 2019 13:37:40 -0400
This is a nice idiom, but I don't think it deserves to take up space in the
standard library.
A better (constexpr-friendly) solution looks something like the following.
My understanding is that Peter Dimov invented it, although I failed to turn
up any "original implementation" in my Google search, and therefore I may
have gotten wrong the details and/or the authorship.
https://godbolt.org/z/LjNqJz
template<class T>
class even_better_no_destructor {
public:
template<class... Args>
constexpr even_better_no_destructor(Args&&... args) :
t(std::forward<Args>(args)...) {}
~even_better_no_destructor() {}
constexpr T& value() { return t; }
private:
union {
char dummy;
T t;
};
};
Notice that `no_destructor<T>` acts like a "wrapped T", but certainly not
like a "function"; therefore it should not have an `operator()`. The
traditional way to unwrap a wrapped T is `.get()`
(tuple/variant/unique_ptr/shared_ptr) or `.value()` (optional/expected).
In C++2a, the current expectation is that you'll be able to use the new
`constinit` keyword to say
static constinit no_destructor<A> a;
to eliminate the guard variable and so on. Once any vendor supports
`constinit`, it would be interesting to see if it works as expected with
this idiom.
–Arthur
On Tue, Aug 13, 2019 at 1:04 PM Walt Karas via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> One approach to the static initialization order problem looks like:
>
> A & a1()
> {
> static A a;
> return(a);
> }
>
> The problem is that, if A had a non-trivial destructor, the static order
> destruction problem could still cause undefined behavior. A solution to
> this, avoiding the call to A::~A, is:
>
> A & a2()
> {
> static A* a(new A);
> return(*a);
> }
>
> This template, no_destructor, avoids the destructor call without
> requiring an unnecessary dynamic allocation:
>
> #include <utility>
> #include <new>
>
> template <typename T>
> class no_destructor
> {
> public:
>
> template <typename ... Args>
> no_destructor(Args && ... args)
> {
> new (payload) T(std::forward<Args>(args)...);
> }
>
> T & operator () () { return(*reinterpret_cast<T *>(payload)); }
>
> private:
>
> alignas(T) char payload[sizeof(T)];
> };
>
> A & a3()
> {
> static no_destructor<A> a;
> return(a());
> }
>
> https://godbolt.org/z/cGSXRy
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
standard library.
A better (constexpr-friendly) solution looks something like the following.
My understanding is that Peter Dimov invented it, although I failed to turn
up any "original implementation" in my Google search, and therefore I may
have gotten wrong the details and/or the authorship.
https://godbolt.org/z/LjNqJz
template<class T>
class even_better_no_destructor {
public:
template<class... Args>
constexpr even_better_no_destructor(Args&&... args) :
t(std::forward<Args>(args)...) {}
~even_better_no_destructor() {}
constexpr T& value() { return t; }
private:
union {
char dummy;
T t;
};
};
Notice that `no_destructor<T>` acts like a "wrapped T", but certainly not
like a "function"; therefore it should not have an `operator()`. The
traditional way to unwrap a wrapped T is `.get()`
(tuple/variant/unique_ptr/shared_ptr) or `.value()` (optional/expected).
In C++2a, the current expectation is that you'll be able to use the new
`constinit` keyword to say
static constinit no_destructor<A> a;
to eliminate the guard variable and so on. Once any vendor supports
`constinit`, it would be interesting to see if it works as expected with
this idiom.
–Arthur
On Tue, Aug 13, 2019 at 1:04 PM Walt Karas via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> One approach to the static initialization order problem looks like:
>
> A & a1()
> {
> static A a;
> return(a);
> }
>
> The problem is that, if A had a non-trivial destructor, the static order
> destruction problem could still cause undefined behavior. A solution to
> this, avoiding the call to A::~A, is:
>
> A & a2()
> {
> static A* a(new A);
> return(*a);
> }
>
> This template, no_destructor, avoids the destructor call without
> requiring an unnecessary dynamic allocation:
>
> #include <utility>
> #include <new>
>
> template <typename T>
> class no_destructor
> {
> public:
>
> template <typename ... Args>
> no_destructor(Args && ... args)
> {
> new (payload) T(std::forward<Args>(args)...);
> }
>
> T & operator () () { return(*reinterpret_cast<T *>(payload)); }
>
> private:
>
> alignas(T) char payload[sizeof(T)];
> };
>
> A & a3()
> {
> static no_destructor<A> a;
> return(a());
> }
>
> https://godbolt.org/z/cGSXRy
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2019-08-13 12:39:52