識別されたユニオン/タグ付きバリアントの発明「コンパイル時にいくつかの条件でデストラクタを自明にする」などの機能に特に必要があると結論付けています。私はある種のSFINAEまたは(疑似コード)のようなものを意味します:
template< typename ...types >
struct X
{
~X() = default((std::is_trivially_destructible< types >{} && ...))
{
// non-trivial code here
}
};
つまり、条件がdefault(*)
の場合true
、デストラクタの定義は と等しくなります~X() = default;
が、代わりfalse
に{ // ... }
ボディが使用されます。
#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;
}
U
クラスをリテラル型にするこの例では (すべてが自明に破壊可能である場合)、クラス ( )first, rest...
とほぼ同じものを定義できますが、デストラクタの定義は必要ありません(つまり、すべての下位型がリテラルの場合はリテラル型です)。 )。次に、テンプレート型エイリアスを定義しますU
V
~U
template< typename ...types >
using W = std::conditional_t< (std::is_trivially_destructible< types >{} && ...), V< types... >, U< types... > >;
using tail = W< rest... >;
との両方U
で再定義しV
ます。したがって、2 つのほぼ同一のクラスがあり、デストラクタの存在のみが異なります。上記のアプローチでは、コードを過度に複製する必要があります。
この問題は、割り当て可能な型を自明にコピー/移動すること、operator =
および型が になる他のすべての条件にも関係していstd::is_trivially_copyable
ました。5 つの条件により、実装する合計 2^5 の組み合わせが得られます。
私が見逃している現在のC ++で表現できる、すぐに使用できるテクニック(および冗長性が低く、上記のテクニックで説明されている)はありますか?
別の考えられるアプローチは、(言語機能) としてデストラクタをマークconstexpr
し、コンパイラに付与して、インスタンス化中に本体が単純なものと同等であるかどうかをテストすることです。
アップデート:
コメントで指摘されているように単純化されたコード: のようなクラスunion
になりました。指定子をunion
削除しました。noexcept