Boostバリアントのクローン

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&&)が優先されます。ユニバーサル参照の代わりに移動セマンティクスが意図されている場合に、新しく導入されたTT&&を使用する場合の一般的なベストプラクティスは何ですか?

一般的なケース(可変個引数テンプレートを使用)では問題が発生しますが、マルチビジターを追加する必要があります。 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 

コメント

  • また、ヘルプセンターによると、これは、’想定どおりに機能しないコードのサイトではありません。行う。コードは機能しますか
  • “が機能する場合”とは、 “コンパイルされますか?”、clang ++バージョン3.3でコンパイルされますが、相互作用のためにg ++バージョン4.8.2ではコンパイルされません。パラメータパックとラムダの間。 CMakeLists.txtファイルは無条件にclang ++を選択します。 “作業”の場合、”は移植可能ですか?”、いいえ、ポータブルではない可能性があります。これは、私がstd::aligned_storageを使用する方法によるものです。関連するtypeのポータブル使用に関するアドバイスは非常に役立ちます。 “作業”とは、”が本来の意図を満たしていることを意味しますか?”、答えはイエスですが、文体的でベストプラクティスのアドバイスが欲しいのですが。
  • std::forwardは問題ないようですが、’使用できなかった場所は見つかりません。 static_assertを使用すると、より適切なエラーメッセージを表示できます。すべてのポインタが同じ境界に配置される保証はないため、移植性が問題になる可能性があります(したがって、void *T*の要件が異なる場合があります)、しかしこれは最近非常に珍しいでしょう。 ‘ブーストがこれをどのように回避するのか、または単に無視するのかわかりません。
  • @Yuushi、まず、これを確認するために時間を割いていただきありがとうございます。移植性のないものは無視していると思います( boost.org/doc/libs/1_55_0/boost/variant/detail/cast_storage.hpp )。ポータブルソリューションを見つけました( github.com/sonyandy/cpp-experiments/blob/master/include/wart/ … std::tupleと同様の手法を使用しますが、unionを使用します。
  • 問題ありません。’ d追加する他の唯一のことは、ファイルが少し”ビジー”そして、それ自体で潜在的に役立つものがあります(’がis_movable など)他の場所に住む可能性があります。残念ながら、コードがかなり複雑であるため、これについて適切なフィードバックを提供するために必要な知識を持っているこのあたりのC ++レビューアの数はおそらく1桁です。

回答

ここにはたくさんあるので、レビューを細かく分割します。まず、メタ機能のセクションに焦点を当てたいと思います。メタ関数は短いかもしれませんが、「正しく行うには非常に強力で重要ですが、正確さと有用性の点で重要です。

まずは:

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およびnoneallの記述方法は問題なく機能しますが、機能しません。他の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自体のレビューは少し後で書きます。これがお役に立てば幸いです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です