C ++の学習の一環として、特にC ++ 11に重点を置いて、Boostのバリアントに相当するものを実装したいと思いました( こちら)。私のコードは Variant.hpp にあり、現在のバージョンは以下のとおりです。 。
std::aligned_storage
を移植可能に使用するにはどうすればよいですか?私の現在のソリューションでは、おそらくstatic_cast
を移植不可能に使用しています。移植性がある場合、その情報は非常に価値があります。特定のコードは、タイプiv id =のvalue
の*static_cast<T*>(static_cast<void*>(&value))
に似ています。 “877d4f443a”>
(...
は、さまざまなテンプレートを示すためのものではありません。
。この特定の用途では、SFINAEの方が優れていますか?SFINAEを使用して、実行可能な関数のセットから過負荷を取り除くことができることは理解していますが、static_assert
を使用する場合そこにあると仮定する実行可能な関数は1つだけですが、実行可能な関数が複数ある場合の例は貴重です。
std::forward
を多用しました。より少ない使用でうまくいくことは可能ですか?
コンストラクターのオーバーロードの1つでstd::enable_if
を使用して、移動時にのみ使用されるようにしました意図されています(variant(U&& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr, typename detail::variant::enable_if_movable<U>::type* = nullptr)
を参照)。両方のenable_if
がない場合、このコンストラクターは、コピーコンストラクターvariant(variant const&)
が代わりに意図されている場合に使用されますが、前者の結果は最終的なコンパイラエラー。この動作を強制するためのより良い方法はありますか?私が試した解決策の1つは、variant(variant const& rhs)
に委任するオーバーロードとしてvariant(variant&)
を含めることでした。これは、オーバーロードルールではvariant(variant const&)
よりもvariant(U&&)
が優先されます。ユニバーサル参照の代わりに移動セマンティクスが意図されている場合に、新しく導入されたT
にT&&
を使用する場合の一般的なベストプラクティスは何ですか?
一般的なケース(可変個引数テンプレートを使用)では問題が発生しますが、マルチビジターを追加する必要があります。 variant
クラスを実装するときに思いついた興味深い点は、テンプレート引数の再配置のみを含むvariant
間の暗黙的な変換、または左辺値テンプレートの場所です。引数は右辺値テンプレートの引数のスーパーセットです。
コメント、質問、アドバイスはすべて大歓迎です。
#ifndef WART_VARIANT_HPP #define WART_VARIANT_HPP #include <type_traits> #include <utility> #include "math.hpp" namespace wart { template <typename... T> class variant; namespace detail { namespace variant { template <typename... T> using variant = wart::variant<T...>; template <typename T> using is_movable = typename std::integral_constant <bool, std::is_rvalue_reference<T&&>::value && !std::is_const<T>::value>; template <typename T, typename U = void> using enable_if_movable = std::enable_if<is_movable<T>::value, U>; template <typename... Types> using union_storage = typename std::aligned_storage <math::max_constant<std::size_t, sizeof(Types)...>::value, math::lcm_constant<std::size_t, std::alignment_of<Types>::value...>::value>::type; template <typename... Types> using union_storage_t = typename union_storage<Types...>::type; template <typename Elem, typename... List> struct elem; template <typename Head, typename... Tail> struct elem<Head, Head, Tail...>: std::true_type {}; template <typename Elem, typename Head, typename... Tail> struct elem<Elem, Head, Tail...>: elem<Elem, Tail...>::type {}; template <typename Elem> struct elem<Elem>: std::false_type {}; template <typename Elem, typename... List> struct elem_index; template <typename Head, typename... Tail> struct elem_index<Head, Head, Tail...>: std::integral_constant<int, 0> {}; template <typename Elem, typename Head, typename... Tail> struct elem_index<Elem, Head, Tail...>: std::integral_constant<int, elem_index<Elem, Tail...>::value + 1> {}; template <bool... List> struct all; template <> struct all<>: std::true_type {}; template <bool... Tail> struct all<true, Tail...>: all<Tail...>::type {}; template <bool... Tail> struct all<false, Tail...>: std::false_type {}; template <typename Elem, typename... List> using enable_if_elem = std::enable_if<elem<Elem, List...>::value>; template <typename F, typename... ArgTypes> using common_result_of = std::common_type<typename std::result_of<F(ArgTypes)>::type...>; struct destroy { template <typename T> void operator()(T&& value) { using type = typename std::remove_reference<T>::type; std::forward<T>(value).~type(); } }; struct copy_construct { void* storage; template <typename T> void operator()(T const& value) { new (storage) T(value); } }; template <typename... T> struct copy_construct_index { void* storage; template <typename U> int operator()(U const& value) { new (storage) U(value); return elem_index<U, T...>::value; } }; struct move_construct { void* storage; template <typename T> typename enable_if_movable<T>::type operator()(T&& value) { new (storage) T(std::move(value)); } }; template <typename... T> struct move_construct_index { void* storage; template <typename U> typename enable_if_movable<U, int>::type operator()(U&& value) { new (storage) U(std::move(value)); return elem_index<U, T...>::value; } }; struct copy_assign { void* storage; template <typename T> void operator()(T const& value) { *static_cast<T*>(storage) = value; } }; template <typename... T> struct copy_assign_reindex { variant<T...>& variant; template <typename U> void operator()(U const& value) { if (variant.which_ == elem_index<U, T...>::value) { *static_cast<U*>(static_cast<void*>(&variant.storage_)) = value; } else { variant.accept(destroy{}); new (&variant.storage_) U(value); variant.which_ = elem_index<U, T...>::value; } } }; struct move_assign { void* storage; template <typename T> typename enable_if_movable<T>::type operator()(T&& value) { *static_cast<T*>(storage) = std::move(value); } }; template <typename... T> struct move_assign_reindex { variant<T...>& variant; template <typename U> typename enable_if_movable<U>::type operator()(U&& value) { if (variant.which_ == elem_index<U, T...>::value) { *static_cast<U*>(static_cast<void*>(&variant.storage_)) = std::move(value); } else { variant.accept(destroy{}); new (&variant.storage_) U(std::move(value)); variant.which_ = elem_index<U, T...>::value; } } }; } } template <typename... T> class variant { int which_; detail::variant::union_storage_t<T...> storage_; public: template <typename F> using result_of = detail::variant::common_result_of<F, T...>; template <typename F> using result_of_t = typename result_of<F>::type; template <typename U> variant(U const& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr): which_{detail::variant::elem_index<U, T...>::value} { new (&storage_) U(value); } template <typename U> variant(U&& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr, typename detail::variant::enable_if_movable<U>::type* = nullptr): which_{detail::variant::elem_index<U, T...>::value} { new (&storage_) U(std::move(value)); } variant(variant const& rhs): which_{rhs.which_} { rhs.accept(detail::variant::copy_construct{&storage_}); } template <typename... U> variant(variant<U...> const& rhs, typename std::enable_if< detail::variant::all<detail::variant::elem<U, T...>::value...>::value >::type* = nullptr): which_{rhs.accept(detail::variant::copy_construct_index<T...>{&storage_})} {} variant(variant&& rhs): which_{rhs.which_} { std::move(rhs).accept(detail::variant::move_construct{&storage_}); } template <typename... U> variant(variant<U...>&& rhs, typename std::enable_if< detail::variant::all<detail::variant::elem<U, T...>::value...>::value >::type* = nullptr): which_{std::move(rhs).accept(detail::variant::move_construct_index<T...>{&storage_})} {} ~variant() { accept(detail::variant::destroy{}); } variant& operator=(variant const& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_copy_constructible<T>::value...>::value, "all template arguments T must be nothrow copy constructible in class template variant"); if (this == &rhs) { return *this; } if (which_ == rhs.which_) { rhs.accept(copy_assign{&storage_}); } else { accept(destroy{}); rhs.accept(copy_construct{&storage_}); which_ = rhs.which_; } return *this; } template <typename... U> variant& operator=(variant<U...> const& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_copy_constructible<T>::value...>::value, "all template arguments T must be nothrow copy constructible in class template variant"); rhs.accept(copy_assign_reindex<T...>{*this}); return *this; } variant& operator=(variant&& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_move_constructible<T>::value...>::value, "all template arguments T must be nothrow move constructible in class template variant"); if (this == &rhs) { return *this; } if (which_ == rhs.which_) { std::move(rhs).accept(move_assign{&storage_}); } else { accept(detail::variant::destroy{}); std::move(rhs).accept(move_construct{&storage_}); which_ = rhs.which_; } return *this; } template <typename... U> variant& operator=(variant<U...>&& rhs) & { using namespace detail::variant; static_assert(all<std::is_nothrow_copy_constructible<T>::value...>::value, "all template arguments T must be nothrow copy constructible in class template variant"); std::move(rhs).accept(move_assign_reindex<T...>{*this}); return *this; } template <typename F> result_of_t<F> accept(F&& f) const& { using namespace detail::variant; using call = result_of_t<F&&> (*)(F&& f, union_storage_t<T...> const&); static call calls[] { [](F&& f, union_storage_t<T...> const& value) { return std::forward<F>(f)(*static_cast<T const*>(static_cast<void const*>(&value))); }... }; return calls[which_](std::forward<F>(f), storage_); } template <typename F> result_of_t<F> accept(F&& f) & { using namespace detail::variant; using call = result_of_t<F&&> (*)(F&& f, union_storage_t<T...>&); static call calls[] { [](F&& f, union_storage_t<T...>& value) { return std::forward<F>(f)(*static_cast<T*>(static_cast<void*>(&value))); }... }; return calls[which_](std::forward<F>(f), storage_); } template <typename F> result_of_t<F> accept(F&& f) && { using namespace detail::variant; using call = result_of_t<F> (*)(F&& f, union_storage_t<T...>&&); static call calls[] { [](F&& f, union_storage_t<T...>&& value) { return std::forward<F>(f)(std::move(*static_cast<T*>(static_cast<void*>(&value)))); }... }; return calls[which_](std::forward<F>(f), std::move(storage_)); } friend struct detail::variant::copy_assign_reindex<T...>; friend struct detail::variant::move_assign_reindex<T...>; }; } #endif
コメント
回答
ここにはたくさんあるので、レビューを細かく分割します。まず、メタ機能のセクションに焦点を当てたいと思います。メタ関数は短いかもしれませんが、「正しく行うには非常に強力で重要ですが、正確さと有用性の点で重要です。
まずは:
template <typename T> using is_movable = typename std::integral_constant <bool, std::is_rvalue_reference<T&&>::value && !std::is_const<T>::value>; template <typename T, typename U = void> using enable_if_movable = std::enable_if<is_movable<T>::value, U>;
最初のものは単に間違っています。あなたはこのメタ関数を使用して、型が構築可能であるかどうかを確認しています(move_construct
内)…しかし、あなたはこれを行っています左辺値参照でもconst
でもないかどうかを確認するだけです。あなたは実際に移動構築に関連するものをチェックしていません。何かが右辺値参照であるからといって、そこから移動できるとは限りません。また、何かが左辺値参照であるからといって、移動できないとは限りません。2つの単純なクラスを考えてみましょう。
struct No { A(A&& ) = delete; }; struct Yes { };
名前が示すように、No
は構築可能ではありません。メタ関数は、構築可能であると言います。また、Yes&
は移動可能ですが、メタ関数はノーと言います。
正しい実装は、標準タイプの特性を使用することです。 std::is_move_constructible
。
次に、エイリアスが疑わしいです。通常、エイリアスを使用して回避する必要があります。 typename ::type
の断片を記述します。あなたはそれをしていません、そして結果として生じる呼び出しはそれほど簡潔ではありません。比較:
typename enable_if_movable<T>::type // yours with alias std::enable_if_t<is_moveable<T>::value> // just using enable_if without alias std::enable_if_t<std::is_move_constructible<T>::value> // just stds
個人的には最後のバージョンをお勧めします。ここではC ++ 14エイリアスを使用していることに注意してください。C++ 14コンパイラがない場合は、それらすべてを使用してメタ関数ライブラリを開始することをお勧めします。単に書くだけです:
template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type;
移動先:
template <typename Elem, typename... List> struct elem;
ありませんelem
がここで何をするかを誰もが知る方法。実装を読むまではしませんでした。これのより適切な名前はcontains
です。しかし、すぐに実装に戻ります。
まず、次のように始めましょう。
template <bool... List> struct all;
all
は非常に便利です。近親者も同様ですany
およびnone
。all
の記述方法は問題なく機能しますが、機能しません。他の2つを書く方が簡単です。これらを書き出す良い方法は、@ Columboのbool_pack
トリックを使用することです:
template <bool...> struct bool_pack; template <bool f, bool... bs> using all_same = std::is_same<bool_pack<f, bs...>, bool_pack<bs..., f>>;
それただあなたのヘルパー。これを使用して、残りすべてを簡単に実装できます。
template <bool... bs> using all_of = all_same<true, bs...>; template <bool... bs> using none_of = all_same<false, bs...>; template <bool... bs> using any_of = std::integral_constant<bool, !none_of<bs...>::value>;
それができたら、contains
ワンライナーとして:
template <typename Elem, typename... List> using contains = any_of<std::is_same<Elem, List>::value...>;
以前と同様に、enable_if_elem
に値が表示されません。そして、common_result_of
は、メタ関数を生成するだけでなく、タイプを取る必要があります。
template <typename F, typename... ArgTypes> using common_result_of = std::common_type_t<std::result_of_t<F(ArgTypes)>::...>;
それをvariant
自体に貼り付ける方が読みやすくなりますが、
// no need to use anything in detail::, unless you need to // write your own aliases for common_type_t and result_of_t template <typename F> using result_of = std::common_type_t<std::result_of_t<F(T)>...>;
使用法について説明します。 。全体を通して、戻り値の型で次のメタ関数を使用します:
template <typename T> typename enable_if_movable<T>::type operator()(T&& value);
またはダミーポインタとして:
template <typename U> variant(U const& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr)
ただし、どちらの場合も、SFINAEロジックを名前のない最終テンプレートパラメーターとして配置すると、複雑なテンプレート式を解析する方がはるかに簡単です。
template <typename T, typename = std::enable_if_t<std::is_move_constructible<T>::value> > void operator()(T&& value); template <typename U, typename = std::enable_if_t<contains<U, T...>::value> > variant(U const& value);
一貫性は理解にも役立ちます。ダミーポインタ引数は、C ++ 03から残った紛らわしいハックです。もう必要ありません。特に 2つのダミーポインタが必要な場合:
template <typename U, typename = std::enable_if_t<contains<U, T...>::value && std::is_move_constructible<U>::value> > variant(U&& value);
この人に関する補足説明。実際にはあなたが望むことをしません。これは任意の右辺値参照ではなく、転送参照です。実際、ここで2つのコンストラクターを一度に組み合わせることができます。
template <typename U, typename V = std::remove_reference_t<U>, typename = std::enable_if_t<contains<V, T...>::value && std::is_constructible<V, U&&>::value> > variant(U&& value) : which_{elem_index<V, T...>::value} { new (&storage_) V(std::forward<U>(value)); }
これにより、コードの別の問題も解決されることに注意してください。つまり、チェックしないでください。クラスがコピー構築可能である場合。バリアントにunique_ptr
のようなものを貼り付けたい場合はどうなりますか。構築可能に移動しますが、構築可能をコピーすることはできませんが、コードでそれをチェックしたことはありません。重要-そうしないと、ユーザーに不可解なエラーメッセージが表示されるだけです。
これでメタ関数の部分は終わりだと思います。 variant
自体のレビューは少し後で書きます。これがお役に立てば幸いです。
std::aligned_storage
を使用する方法によるものです。関連するtype
のポータブル使用に関するアドバイスは非常に役立ちます。 “作業”とは、”が本来の意図を満たしていることを意味しますか?”、答えはイエスですが、文体的でベストプラクティスのアドバイスが欲しいのですが。std::forward
は問題ないようですが、’使用できなかった場所は見つかりません。static_assert
を使用すると、より適切なエラーメッセージを表示できます。すべてのポインタが同じ境界に配置される保証はないため、移植性が問題になる可能性があります(したがって、void *
とT*
の要件が異なる場合があります)、しかしこれは最近非常に珍しいでしょう。 ‘ブーストがこれをどのように回避するのか、または単に無視するのかわかりません。std::tuple
と同様の手法を使用しますが、union
を使用します。is_movable
や