Inventing a discriminated union/tagged variant I conclude that there is particular need in such a feature as "make destructor trivial on some conditions at compile time". I mean some kind of SFINAE or something like (pseudocode):
template< typename ...types >
struct X
{
~X() = default((std::is_trivially_destructible< types >{} && ...))
{
// non-trivial code here
}
};
Which means that if condition in default(*)
is true
, then definition of destructor is equal to ~X() = default;
, but if it is false
then { // ... }
body used instead.
#pragma once
#include <type_traits>
#include <utility>
#include <experimental/optional>
#include <cassert>
template< typename ...types >
class U;
template<>
class U<>
{
U() = delete;
U(U &) = delete;
U(U const &) = delete;
U(U &&) = delete;
U(U const &&) = delete;
void operator = (U &) = delete;
void operator = (U const &) = delete;
void operator = (U &&) = delete;
void operator = (U const &&) = delete;
};
template< typename first, typename ...rest >
class U< first, rest... >
{
struct head
{
std::size_t which_;
first value_;
template< typename ...types >
constexpr
head(std::experimental::in_place_t, types &&... _values)
: which_{sizeof...(rest)}
, value_(std::forward< types >(_values)...)
{ ; }
template< typename type >
constexpr
head(type && _value)
: head(std::experimental::in_place, std::forward< type >(_value))
{ ; }
};
using tail = U< rest... >;
union
{
head head_;
tail tail_;
};
template< typename ...types >
constexpr
U(std::true_type, types &&... _values)
: head_(std::forward< types >(_values)...)
{ ; }
template< typename ...types >
constexpr
U(std::false_type, types &&... _values)
: tail_(std::forward< types >(_values)...)
{ ; }
public :
using this_type = first; // place for recursive_wrapper filtering
constexpr
std::size_t
which() const
{
return head_.which_;
}
constexpr
U()
: U(typename std::is_default_constructible< this_type >::type{}, std::experimental::in_place)
{ ; }
U(U &) = delete;
U(U const &) = delete;
U(U &&) = delete;
U(U const &&) = delete;
template< typename type >
constexpr
U(type && _value)
: U(typename std::is_same< this_type, std::decay_t< type > >::type{}, std::forward< type >(_value))
{ ; }
template< typename ...types >
constexpr
U(std::experimental::in_place_t, types &&... _values)
: U(typename std::is_constructible< this_type, types... >::type{}, std::experimental::in_place, std::forward< types >(_values)...)
{ ; }
void operator = (U &) = delete;
void operator = (U const &) = delete;
void operator = (U &&) = delete;
void operator = (U const &&) = delete;
template< typename type >
constexpr
void
operator = (type && _value) &
{
operator std::decay_t< type > & () = std::forward< type >(_value);
}
constexpr
explicit
operator this_type & () &
{
assert(sizeof...(rest) == which());
return head_.value_;
}
constexpr
explicit
operator this_type const & () const &
{
assert(sizeof...(rest) == which());
return head_.value_;
}
constexpr
explicit
operator this_type && () &&
{
assert(sizeof...(rest) == which());
return std::move(head_.value_);
}
constexpr
explicit
operator this_type const && () const &&
{
assert(sizeof...(rest) == which());
return std::move(head_.value_);
}
template< typename type >
constexpr
explicit
operator type & () &
{
return static_cast< type & >(tail_);
}
template< typename type >
constexpr
explicit
operator type const & () const &
{
return static_cast< type const & >(tail_);
}
template< typename type >
constexpr
explicit
operator type && () &&
{
//return static_cast< type && >(std::move(tail_)); // There is known clang++ bug #19917 for static_cast to rvalue reference.
return static_cast< type && >(static_cast< type & >(tail_)); // workaround
}
template< typename type >
constexpr
explicit
operator type const && () const &&
{
//return static_cast< type const && >(std::move(tail_));
return static_cast< type const && >(static_cast< type const & >(tail_));
}
~U()
{
if (which() == sizeof...(rest)) {
head_.~head();
} else {
tail_.~tail();
}
}
};
// main.cpp
#include <cstdlib>
int
main()
{
U< int, double > u{1.0};
assert(static_cast< double >(u) == 1.0);
u = 0.0;
assert(static_cast< double >(u) == 0.0);
U< int, double > w{1};
assert(static_cast< int >(w) == 1);
return EXIT_SUCCESS;
}
In this example for making the class U
a literal type (in case of first, rest...
are all the trivially destructible) it is possible to define almost the same as U
class (V
), but without definition of a destructor ~U
(i.e. is literal type if all descending types are literals). Then define template type alias
template< typename ...types >
using W = std::conditional_t< (std::is_trivially_destructible< types >{} && ...), V< types... >, U< types... > >;
and redefine using tail = W< rest... >;
in both U
and V
. Therefore, there are two almost identical classes, differs only in presence of destructor. Above approach requires excessive duplication of code.
The problem also concerned with trivially copy/move assignable types and operator =
and also all other conditions for type to be std::is_trivially_copyable
. 5 conditions gives totally a 2^5 combinations to implement.
Is there any ready to use technique (and less verbose, then described above one) expressible in present C++ I miss, or maybe coming soon proposal ?
Another thinkable approach is (language feature) to mark the destructor as constexpr
and grant to the compiler to test whether the body is equivalent to trivial one during instantiation or not.
UPDATE:
Code simplified as pointed out in comments: union
became union
-like class. Removed noexcept
specifiers.
A trivial destructor is a destructor that performs no action. Objects with trivial destructors don't require a delete-expression and may be disposed of by simply deallocating their storage. All data types compatible with the C language (POD types) are trivially destructible.
The default destructor calls the destructors of the base class and members of the derived class. The destructors of base classes and members are called in the reverse order of the completion of their constructor: The destructor for a class object is called before destructors for members and bases are called.
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ).
when the object goes out of scope; when the object containing it is destroyed.
Conditional destructor can be implemented via additional intermediate layer with template specialization. For example:
Live Demo on Coliru
#include <type_traits>
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
class storage
{
aligned_storage_t<sizeof(T)> buf;
storage(storage&&) = delete;
public:
storage()
{
new (&buf) T{};
}
T &operator*()
{
return *static_cast<T*>(&buf);
}
void destroy()
{
(**this).~T();
}
};
template<typename T, bool destructor>
struct conditional_storage_destructor
{
storage<T> x;
};
template<typename T>
struct conditional_storage_destructor<T, true> : protected storage<T>
{
storage<T> x;
~conditional_storage_destructor()
{
x.destroy();
}
};
template<typename T>
class wrapper
{
conditional_storage_destructor<T, not is_trivially_destructible<T>::value> x;
public:
T &operator*()
{
return *(x.x);
}
};
int main()
{
static_assert(is_trivially_destructible< wrapper<int> >::value);
static_assert(not is_trivially_destructible< wrapper<vector<int>> >::value);
cout << "executed" << endl;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With