c++ - Gibt es eine gute Möglichkeit, einen bedingten Typ mit einem Standardfehlerfall zu implementieren?




c++11 templates (2)

Nur zum Spaß ... was ist mit std::tuple std::tuple_element und std::tuple_element , die überhaupt std::conditional vermeiden?

Wenn Sie C ++ 14 verwenden können (also Vorlagenvariablen und Spezialisierung von Vorlagenvariablen), können Sie eine Vorlagenvariable für die Konvertierungsgröße / den Index im Tupel schreiben

template <std::size_t>
constexpr std::size_t  bt_index = 100u; // bad value

template <> constexpr std::size_t  bt_index<8u>  = 0u; 
template <> constexpr std::size_t  bt_index<16u> = 1u; 
template <> constexpr std::size_t  bt_index<32u> = 2u; 
template <> constexpr std::size_t  bt_index<64u> = 3u; 

so wird bit_type

template <std::size_t N>
using bit_type = std::tuple_element_t<bt_index<N>,
   std::tuple<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>>;

Wenn Sie nur C ++ 11 verwenden können, können Sie eine bt_index() constexpr Funktion entwickeln, die den richtigen (oder falschen) Wert constexpr .

Sie können überprüfen, ob Sie zufrieden sind

static_assert( std::is_same_v<bit_type<8u>,  std::uint8_t>, "!" );
static_assert( std::is_same_v<bit_type<16u>, std::uint16_t>, "!" );
static_assert( std::is_same_v<bit_type<32u>, std::uint32_t>, "!" );
static_assert( std::is_same_v<bit_type<64u>, std::uint64_t>, "!" );

und das mit bit_type mit einer nicht unterstützten Dimension

bit_type<42u> * pbt42;

einen Kompilierungsfehler verursachen.

- BEARBEITEN - Wie von Jonathan Wakely vorgeschlagen, können Sie, wenn Sie C ++ 20 verwenden, also std::ispow2() und std::log2p1() , vieles vereinfachen: Sie können bt_index überhaupt vermeiden und einfach schreiben

template <std::size_t N>
using bit_type = std::tuple_element_t<std::ispow2(N) ? std::log2p1(N)-4u : -1,
   std::tuple<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>>;

Um einen bedingten Typ zu implementieren, genieße ich std::conditional_t da es den Code kurz und gut lesbar hält:

template<std::size_t N>
using bit_type =
    std::conditional_t<N == std::size_t{  8 }, std::uint8_t,
    std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
    std::conditional_t<N == std::size_t{ 32 }, std::uint32_t, 
    std::conditional_t<N == std::size_t{ 64 }, std::uint64_t, void>>>>;

Die Benutzung funktioniert sehr intuitiv:

bit_type<8u> a;  // == std::uint8_t
bit_type<16u> b; // == std::uint16_t
bit_type<32u> c; // == std::uint32_t
bit_type<64u> d; // == std::uint64_t

Da es sich jedoch um einen reinen bedingten Typ handelt, muss ein Standardtyp vorhanden sein - in diesem Fall void . Wenn N also ein anderer Wert ist, ergibt dieser Typ:

bit_type<500u> f; // == void

Dies wird jetzt nicht kompiliert, aber der ausgebende Typ ist immer noch gültig.

Das heißt, Sie könnten sagen, bit_type<500u>* f; und hätte ein gültiges programm!

Gibt es eine gute Möglichkeit, die Kompilierung fehlschlagen zu lassen, wenn der Fail-Fall eines bedingten Typs erreicht ist?

Eine sofortige Idee wäre, das letzte std::conditional_t durch std::enable_if_t zu ersetzen:

template<std::size_t N>
using bit_type =
    std::conditional_t<N == std::size_t{  8 }, std::uint8_t,
    std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
    std::conditional_t<N == std::size_t{ 32 }, std::uint32_t, 
    std::enable_if_t<  N == std::size_t{ 64 }, std::uint64_t>>>>;

Das Problem dabei ist, dass Vorlagen immer vollständig ausgewertet werden, was bedeutet, dass std::enable_if_t immer vollständig ausgewertet wird - und das std::enable_if_t fehl, wenn N != std::size_t{ 64 } . Urgh.

Meine derzeitige Umgehung dieses Problems ist ziemlich umständlich, wenn ich eine Struktur einführe und 3 Deklarationen verwende:

template<std::size_t N>
struct bit_type {
private:
    using vtype =
        std::conditional_t<N == std::size_t{ 8 }, std::uint8_t,
        std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
        std::conditional_t<N == std::size_t{ 32 }, std::uint32_t,
        std::conditional_t<N == std::size_t{ 64 }, std::uint64_t, void>>>>;

public:
    using type = std::enable_if_t<!std::is_same_v<vtype, void>, vtype>;
};

template<std::size_t N>
using bit_type_t = bit_type<N>::type;

static_assert(std::is_same_v<bit_type_t<64u>, std::uint64_t>, "");

Was im Allgemeinen funktioniert, aber ich mag es nicht, weil es so viel Zeug hinzufügt, dass ich genauso gut die Template-Spezialisierung verwenden könnte. Es behält sich auch die void als besonderen Typ vor - daher funktioniert es nicht, wenn die void tatsächlich eine Rendite einer Niederlassung ist. Gibt es eine lesbare, kurze Lösung?


Sie können dieses Problem lösen, indem Sie eine Indirektionsebene hinzufügen, sodass das Ergebnis der äußersten conditional_t kein Typ ist, sondern eine Metafunktion, auf die ::type angewendet werden muss. Verwenden enable_if dann enable_if anstelle von enable_if_t damit Sie nur dann auf den ::type zugreifen können, wenn er tatsächlich benötigt wird:

template<typename T> struct identity { using type = T; };

template<std::size_t N>
using bit_type = typename
    std::conditional_t<N == std::size_t{  8 }, identity<std::uint8_t>,
    std::conditional_t<N == std::size_t{ 16 }, identity<std::uint16_t>,
    std::conditional_t<N == std::size_t{ 32 }, identity<std::uint32_t>, 
    std::enable_if<N == std::size_t{ 64 }, std::uint64_t>>>>::type;

In dieser Version lautet der Typ im letzten Zweig enable_if< condition , uint64_t> was immer ein gültiger Typ ist, und Sie erhalten nur dann eine Fehlermeldung, wenn dieser Zweig tatsächlich belegt ist und enable_if<false, uint64_t>::type benötigt wird. Wenn eine der früheren Verzweigungen verwendet wird, wird letztendlich für einen der kleineren Ganzzahltypen die identity<uintNN_t>::type verwendet, und es spielt keine Rolle, enable_if<false, uint64_t> keinen verschachtelten Typ hat (weil Sie dies nicht tun) benutze es).





conditional-types