A Boost Variant klónja

A C ++ tanulásának részeként, különös hangsúlyt fektetve a C ++ 11-re, a Boost Variant megfelelőjét akartam megvalósítani ( itt ). A kódom a variant.hpp címen érhető el, az alábbiakban megadott jelenlegi verzióval .

Hogyan használható a std::aligned_storage hordozhatóan? A jelenlegi megoldásom valószínűleg nem hordozható módon használja a static_cast -t ha hordozható, akkor az információ nagyon értékes lenne. Az adott kód hasonló a *static_cast<T*>(static_cast<void*>(&value)) fájlhoz, a value típusú typename std::aligned_storage<...>::type (ahol az ... nem variadikus sablonokat hivatott jelölni).

Némi hasznát veszem a static_assert. Ebben a konkrét alkalmazásban jobb lenne a SFINAE? Megértem, hogy az SFINAE használható a túlterhelések kivágására az életképes függvénykészletből, de ahol static_assert I feltételezem ott csak egy életképes függvény lenne, bár értékes példákat találnék azokra az esetekre, amikor egynél több életképes funkció létezik.

Sokat használtam a std::forward fájlt. Kevesebb felhasználással lehet boldogulni?

Kihasználtam a std::enable_if -t az egyik konstruktor túlterhelésén, hogy biztosítsam, hogy csak mozgáskor használják célja (lásd: variant(U&& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr, typename detail::variant::enable_if_movable<U>::type* = nullptr)). Mindkettő enable_if s nélkül ezt a kivitelezőt akkor használnánk, ha a variant(variant const&) másolatkonstruktort szándékoznánk használni, annak ellenére, hogy az előbbi egy esetleges fordítói hiba. Van-e jobb módja ennek a viselkedésnek az erőltetésére? Az egyik megoldás, amelyet megpróbáltam, az variant(variant&) felvétele túlterhelésként, amely csak a variant(variant const& rhs) -re csúszik – ez a , míg a variant(U&&) -t a túlterhelési szabályok előnyben részesítik a variant(variant const&) -vel szemben. Milyen általános gyakorlat a T&& használata néhány újonnan bevezetett T esetében, amikor az univerzális referencia helyett az elmozdulás szemantikáját szánják?

Még mindig hozzá kell adnom a multivizitorokat, bár ezzel kapcsolatban általános problémák adódnak (variadikus sablonok használata). Valami érdekes dolog, ami az variant osztály megvalósításakor merült fel, az implicit konverzió volt variant között, amelyek csak a sablon argumentumainak átrendezését jelentették, vagy ahol az lvalue sablon Az argumentumok az rvalue sablon argumentumainak halmaza.

Minden megjegyzés, kérdés vagy tanács nagyon értékelhető.

#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 

Megjegyzések

  • Szintén a súgónkban található, ez nem az a webhely, ahol a kód nem ‘ teszi azt, amire kéne csináld. A kódod működik ?
  • Ha ” szerint ” működik, akkor azt érted, ” fordít? “, clang ++ 3.3 verzióval fordít, de interakció miatt nem fordít g ++ 4.8.2 verzióval a paramétercsomagok és a lambdas között. A CMakeLists.txt fájl feltétel nélkül választja ki a clang ++ alkalmazást. Ha a ” munka ” kifejezés alatt azt érted, hogy ” ez hordozható? “, akkor nem, lehet, hogy nem hordozható. Ennek oka az std::aligned_storage használat módja. A társított type hordozható használatával kapcsolatos tanácsok nagyon hasznosak lennének. Ha ” munka ” kifejezés alatt azt érted, hogy ” megfelel-e az eredeti szándéknak? div id = “9e787a6984”>

, a válasz igen, bár nagyon szeretnék stilisztikai és bevált gyakorlati tanácsokat.

  • A std::forward jól néz ki, ‘ nem látok olyan helyet, ahol nem használhatta. Inkább a static_assert -t részesítem előnyben, mivel ez lehetővé teszi, hogy jobb hibaüzeneteket adjon. A hordozhatóság potenciálisan problémát jelent, mivel nincs garancia arra, hogy az összes mutató ugyanazon a határon van elhelyezve (tehát void * és T* eltérő követelmények lehetnek ), de ez manapság nagyon szokatlan lenne. Nem tudom, ‘ nem tudom, hogy a lendület hogyan kerüli ezt körül, vagy ha csak figyelmen kívül hagyják.
  • @ Yuushi, először is köszönöm, hogy szánt időt ennek áttekintésére. . Szerintem csak figyelmen kívül hagynak minden nem hordozható dolgot ( boost.org/doc/libs/1_55_0/boost/variant/detail/cast_storage.hpp ). Találtam egy hordozható megoldást ( github.com/sonyandy/cpp-experiments/blob/master/include/wart/… ), amely hasonló technikát használ, mint a std::tuple, de union.
  • Semmi probléma.Az egyetlen dolog, amit ‘ hozzáadok, az, hogy a fájl kissé ” elfoglaltnak tűnik ” és vannak benne olyan dolgok, amelyek potenciálisan önmagukban is hasznosak (olyan tulajdonságok, amelyeket ‘ definiált, mint például is_movable és enable_if_movable például), amelyek esetleg valahol másutt élhetnek. Sajnos az itteni C ++ véleményezők száma, akik rendelkeznek a megfelelő visszajelzéshez szükséges ismeretekkel, valószínűleg egy számjegyű, mivel a kód meglehetősen összetett.
  • Válasz

    Nagyon sok itt van, ezért darabokra fogom bontani a véleményemet. Először csak a metafunkciós szakaszra szeretnék koncentrálni. Lehet, hogy a metafunkciók rövidek, de nagyon fontosak és fontosak a helyes helyzethez – de helyességüket és hasznosságukat tekintve.

    Kezdésként:

    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>; 

    Az első egyszerűen téves. Ezt a metafunkciót használja annak ellenőrzésére, hogy egy típus felépíthető-e (a move_construct mezőben) … de ezt megcsinálja csak annak ellenőrzésével, hogy ez nem egy értékérték-e, vagy const. Valójában nem ellenőriz semmit az építkezés áthelyezésével kapcsolatban. Az, hogy valami értéke referenciaérték, még nem jelenti azt, hogy elmozdulhatna onnan. És csak azért, mert valami értéke referenciaérték, még nem jelenti azt, hogy nem lehet. Fontoljon meg két egyszerű osztályt:

    struct No { A(A&& ) = delete; }; struct Yes { }; 

    Ahogy a neve is sugallja, a No nem mozdítható meg. A metafunkció azt mondja, hogy van. Továbbá, Yes& szerkeszthető, de a metafunkció nemet mond.

    A helyes megvalósítás az lenne, ha egyszerűen használná a szokásos típusú tulajdonságot std::is_move_constructible .

    Másodszor, az ott található álnév megkérdőjelezhető. Általában álneveket használunk arra, hogy elkerüljük írja a typename ::type Cruft-ot. Ezt nem teszed meg, és az ebből fakadó hívás nem lesz sokkal tömörebb. Összehasonlítás:

    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 

    Személy szerint az utolsó verziót preferálnám. Ne feledje, hogy itt a C ++ 14 álnevet használom. Ha nincs C ++ 14 fordítója, akkor érdemes megkezdeni a metafunkciós könyvtárat mindegyikkel. Csak annyit kell írniuk, hogy:

    template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type; 

    Áttérés:

    template <typename Elem, typename... List> struct elem; 

    Nincs így bárki megtudhatja, mit csinál itt elem. Addig nem tudtam, amíg el nem olvastam a megvalósítást. Ennek sokkal jobb neve contains lenne. De egy pillanat múlva visszatérek a megvalósításhoz.

    Először is kezdjük a következővel:

    template <bool... List> struct all; 

    all nagyon hasznos. Így vannak a közeli rokonai is any és none. A all írásmódja rendben van és működik, de nem készül könnyebb megírni a másik kettőt. Ezeknek a kiírásának jó módja a @Columbo “s bool_pack trükk használata:

    template <bool...> struct bool_pack; template <bool f, bool... bs> using all_same = std::is_same<bool_pack<f, bs...>, bool_pack<bs..., f>>; 

    Ez” s csak a segítője. Használhatja ezt az összes többi egyszerű megvalósításához:

    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>; 

    És ha ez megvan, akkor újra megvalósíthatjuk a contains egysorosként:

    template <typename Elem, typename... List> using contains = any_of<std::is_same<Elem, List>::value...>; 

    A korábbiakhoz hasonlóan nem látom az értéket a enable_if_elem És a common_result_of -nek meg kell adnia a type t, nem csak a metafunkciót kell megadnia:

    template <typename F, typename... ArgTypes> using common_result_of = std::common_type_t<std::result_of_t<F(ArgTypes)>::...>; 

    Bár könnyebben olvasható, ha ezt csak magába ragasztja: 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)>...>; 

    Most a használatra . Végig ezeket a metafunkciókat használja a visszatérési típusban:

    template <typename T> typename enable_if_movable<T>::type operator()(T&& value); 

    Vagy dummy pointerként:

    template <typename U> variant(U const& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr) 

    De mindkét esetben sokkal egyszerűbbnek találom az összetett sablonkifejezések elemzését, ha az SFINAE logikát meg nem nevezett végső sablonparaméterként helyezzük el:

    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); 

    A következetesség segít a megértésben is. A dummy pointer argumentum zavaró hackmaradvány a C ++ 03-ból. Nincs rá többé szükség. Különösen akkor, ha két dummy pointerre van szükséged:

     template <typename U, typename = std::enable_if_t<contains<U, T...>::value && std::is_move_constructible<U>::value> > variant(U&& value); 

    Mellékjegyzet erről a fickóról. valójában nem azt csinálja, amit akar. Ez nem önkényes érték hivatkozás – ez egy továbbítási referencia. Valójában akár itt is össze tudjuk kapcsolni a két konstruktort:

    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)); } 

    Ne feledje, hogy ez egy másik problémát is megold a kódjával – mégpedig azt, hogy soha nem ellenőrizheti ha egy osztály másolattal készíthető. Mi lenne, ha valami olyasmit szeretnél ragasztani a változatodba, hogy unique_ptr. Mozgassa a szerkeszthetőt, de ne másolja a szerkeszthetőt – de ezt soha nem ellenőrizte a kódban. Fontos – különben a felhasználók csak rejtélyes hibaüzeneteket kapnának.

    Úgy gondolom, hogy ezzel befejeződik a metafunkciós rész. Kicsit később írok egy áttekintést magáról az variant -ről.Remélem, hasznosnak találja ezt.

    Vélemény, hozzászólás?

    Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük