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.
types...
). – NewelX
to store the values and manage destruction directly. Perhaps have a member or base templated on thestd::is_trivially_destructible
value, then you can specialiseMember_Or_Base<false>
's destructor to preform desired destruction. And that's way too much code to post - when you have a specific issue create minimal code illustrating it. – CondescendX
, but atU
. In C++union
s can't have a base classes of to be a base classes. – MidgutU
? It is evident, that the problem really exist. – MidgutDefine the non-trivial destructor
: already done. See the code ofU
. – Midgut~U() { if (active()) { head_.~head(); } else { tail_.~tail(); } }
– Midguthead_.~head()
when it will be invoked anyway becausehead_
is non-pointer and the object which is holding it is getting destroyed? – NewelU
isunion
, but notstruct
orclass
? You must provide a destructor if any of underlying types is non-trivially destructible. – Midgut~U() { destructor<is_trivially_destructible<types>{} && ..>::destruct(head_, tail_); }
– Newelconstexpr U< int, char > u{1};
<=> due to non-triviality of destructorU
is not literal. – Midgutconstexpr
then why would you useU
in the first place? Why not useint
directly? – Newelstruct V<Head, Tail...>
that contains aunion { Head h; V<Tail...> tail; };
rather than a plain union. – Mcauleystorage<I, T, Ts...>
) contains a standard-layout structindexed<T>
and a standard-layout unionstorage<I+1, Ts...>
; it doesn't contain "several standard-layout structs" - only one. As far as I can tell, the standard doesn't provide for recursing into union members of unions for this purpose. – Mcauleyunion { Head h; V<Tail...> tail; };
gives nothing. – Midgutint
directly, because there is an idiom to implement. It make a big sense and matters much in fairly wide variety of applications. You can see full code (U
isversatile
here) of variant, which usesU
as a storage for values of bounded types. The example of use is recursive structures like AST. – Midgutoperator=
if non-trivial in a crtp base, and conditionally haveoperator=
in the child type either be non-trivial call-crtp, or be assignment from a non-reachable type. I don;t think that will block operator= creation, but not certain. – Admiraltystruct
not deny the nessesity of defining (or defaulting) destructors, copy/move assignment operators, copy/move constructors for underlying union. Looking atstd::is_trivially_copyable
class requirements tell me there are at least 2^5 = 32 possible combinations of "triviallity" of member functions (even avoidingconst &&
,&
,volatile
cases for c-tor and assignment operator). – Midgutrequires
clause but this question is tagged [C++11]/[C++14] only. – Glomerulonephritis