Clone of Boost Variant (Norsk)

Som en del av å lære C ++, med spesiell vekt på C ++ 11, ønsket jeg å implementere ekvivalent av Boosts Variant (ligger her ). Koden min er tilgjengelig på variant.hpp , med den nåværende versjonen gitt nedenfor .

Hvordan kan std::aligned_storage brukes bærbart? Min nåværende løsning gjør sannsynligvis ikke-bærbar bruk av static_cast hvis den er bærbar, vil denne informasjonen være veldig verdifull. Den spesifikke koden ligner *static_cast<T*>(static_cast<void*>(&value)), for value av typen typename std::aligned_storage<...>::type (der ... ikke er ment å indikere variadiske maler).

Jeg bruker litt static_assert. Ville SFINAE være bedre i denne bruken? Jeg forstår at SFINAE kan brukes til å beskjære overbelastning fra settet med levedyktige funksjoner, men der jeg bruker static_assert I anta der ville bare være en levedyktig funksjon, selv om jeg ville finne verdifulle eksempler på tilfeller der det er mer enn en levedyktig funksjon.

Jeg brukte mye std::forward. Er det mulig å klare seg med færre bruksområder?

Jeg brukte std::enable_if på en av konstruktørens overbelastninger for å sikre at den bare ville bli brukt når et trekk er ment (se variant(U&& value, typename detail::variant::enable_if_elem<U, T...>::type* = nullptr, typename detail::variant::enable_if_movable<U>::type* = nullptr)). Uten begge enable_if s, vil denne konstruksjonen brukes når kopikonstruktøren variant(variant const&) i stedet er ment, selv om førstnevnte resulterer i en eventuell kompilatorfeil. Er det en bedre måte å tvinge denne oppførselen på? En løsning jeg prøvde var å ta med variant(variant&) som en overbelastning som bare overgår til variant(variant const& rhs) – den ville blitt valgt over variant(U&&), mens variant(U&&) foretrekkes fremfor variant(variant const&) av overbelastningsreglene. Hva er den generelle fremgangsmåten når du bruker T&& for noen nylig introduserte T når flytte semantikk, i stedet for en universell referanse, er ment?

Jeg trenger fortsatt å legge til multivisatorer, selv om jeg generelt har problemer med dette (ved hjelp av variabelmaler). Noe interessant som kom opp ved implementering av variant klassen var implisitte konverteringer mellom variant som bare innebar omorganisering av malargumentene eller hvor malverdien argumenter er et overordnet sett av argumenter for rvalue-malen.

Alle kommentarer, spørsmål eller råd er veldig verdsatt.

#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 

Kommentarer

  • I tillegg til vårt brukerstøtte er dette ikke nettstedet for kode som ikke ‘ ikke gjør det det skal gjøre. Fungerer koden din ?
  • Hvis du ved » «, mener du, » kompilerer den? «, kompilerer den med clang ++ versjon 3.3, men kompilerer ikke med g ++ versjon 4.8.2 på grunn av en interaksjon mellom parameterpakker og lambdas. CMakeLists.txt-filen velger clang ++ ubetinget. Hvis du ved » arbeider «, mener du » er den bærbar? «, så nei, det er kanskje ikke bærbart. Dette skyldes måten jeg bruker std::aligned_storage på. Råd om bærbar bruk av tilhørende type vil være veldig nyttig. Hvis du ved » arbeider «, mener du » tilfredsstiller den den opprinnelige hensikten? «, svaret er ja, selv om jeg veldig gjerne vil ha stilistiske og beste råd.
  • Bruk av std::forward ser bra ut, jeg kan ‘ ikke se noe sted der du ikke kunne bruke det. Jeg foretrekker static_assert da det lar deg gi bedre feilmeldinger. Bærbarhet er potensielt et problem, da det ikke er noen garanti for at alle pekere er justert på samme grense (så void * og T* kan ha forskjellige krav ), men dette ville være veldig uvanlig i disse dager. Jeg vet ikke ‘ hvordan boost kommer rundt dette, eller om de bare ignorerer det.
  • @Yuushi, først, takk for at du tok deg tid til å gjennomgå dette . Jeg tror de bare ignorerer alle ikke-bærbare ting ( boost.org/doc/libs/1_55_0/boost/variant/detail/cast_storage.hpp ). Jeg fant en bærbar løsning ( github.com/sonyandy/cpp-experiments/blob/master/include/wart/… ) som bruker en teknikk som ligner på det std::tuple bruker, men med union.
  • Ikke noe problem.Den eneste andre tingen jeg ‘ la til, er at filen virker litt » opptatt » og det er ting i det som potensielt er nyttige i seg selv (trekk som du ‘ har definert som is_movable og enable_if_movable, for eksempel) som potensielt kan bo et annet sted. Dessverre er antallet C ++ -anmeldere her med den kunnskapen som trengs for å gi deg gode tilbakemeldinger på dette trolig i enkeltsifrene, da koden er ganske kompleks.

Svar

Det er mye her, så jeg skal dele anmeldelsen min i stykker. Jeg vil starte med å bare fokusere på metafunksjonsseksjonen. Metafunksjoner kan være korte, men de er veldig kraftige og viktige for å få rett – men når det gjelder korrekthet og nytte.

Til å begynne med:

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

Den første er rett og slett feil. Du bruker denne metafunksjonen for å sjekke om en type er flyttbar (i move_construct) … men du gjør dette ved å bare sjekke om den ikke er en referanseverdi eller const. Du sjekker faktisk ikke noe relatert til flyttingskonstruksjon. Bare fordi noe er en referanseverdi, betyr ikke det at du kan flytte fra det. Og bare fordi noe er en referanseverdi, betyr ikke det at du ikke kan. Tenk på to enkle klasser:

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

Som navnet antyder, er No ikke flyttbar. Metafunksjonen din sier at den er. Også, Yes& er flyttbar, men metafunksjonen din sier nei.

Den riktige implementeringen vil være å bare bruke standardtypen std::is_move_constructible .

For det andre er aliaset det tvilsomt. Vanligvis bruker vi aliaser for å unngå å måtte skriv typename ::type cruft. Du gjør ikke det, og den resulterende samtalen er ikke så mye mer kortfattet. Sammenlign:

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 

Jeg foretrekker den siste versjonen personlig. Merk at jeg bruker C ++ 14-aliaset her. Hvis du ikke har en C ++ 14-kompilator, er det absolutt verdt det å starte metafunksjonsbiblioteket med dem alle. De er bare å skrive:

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

Gå videre til:

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

Det er ingen slik at hvem som helst vil vite hva elem gjør her. Jeg gjorde ikke før jeg leste implementeringen. Et mye bedre navn på dette ville være contains. Men jeg kommer tilbake til implementeringen om et øyeblikk.

La oss først starte med:

template <bool... List> struct all; 

all er veldig nyttig. Det samme er nære slektninger any og none. Måten du skrev på all er greit og fungerer, men gjør ikke det er lettere å skrive de to andre. En god måte å skrive disse ut på er å bruke @Columbo «s bool_pack triks:

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

That» s bare hjelperen din. Du kan bruke det til å implementere resten lett:

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

Og når vi har det, kan vi implementere contains som en linje:

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

På samme måte som før ser jeg ikke verdien i enable_if_elem Og common_result_of bør ta typen , ikke bare gi metafunksjonen:

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

Selv om det er mer lesbart å bare holde det i variant seg selv:

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

Nå på bruken . Gjennomgående bruker du disse metafunksjonene i returtypen:

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

Eller som en dummypeker:

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

Men i begge tilfeller synes jeg det er mye lettere å analysere komplekse maluttrykk hvis du setter SFINAE-logikken som en navngitt endelig malparameter:

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

Konsistensen hjelper forståelsen også. Dummy pointer-argumentet er en forvirrende hackrest fra C ++ 03. Det er ikke noe behov for det lenger. Spesielt når du trenger to dummy-pekere:

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

Sidebeskrivelse til denne fyren. Det gjør faktisk ikke det du vil. Dette er ikke en vilkårlig referanseverdi – det er en videresendingsreferanse. Faktisk kan vi til og med kombinere de to konstruktørene her på en gang:

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

Merk at dette også løser et annet problem med koden din – nemlig at du aldri sjekker hvis en klasse er kopibyggbar. Hva om du ville stikke noe sånt som unique_ptr i din variant. Flytt konstruerbart, men ikke kopier konstruerbart – men du sjekket det aldri i koden din. Viktig – ellers vil brukerne dine bare få kryptiske feilmeldinger.

Jeg tror dette avslutter metafunksjonsdelen. Jeg vil skrive en anmeldelse av variant seg selv litt senere.Håper du synes dette er nyttig.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *